The ASCII Darkroom
The ASCII Darkroom: a photo pipeline where the photos are made of characters and the chemistry is arithmetic. One NEGATIVE comes in; the enlarger develops it into a print; then three derivative baths run in PARALLEL off the same developed print - high-contrast, thumbnail, vignette - and everything is promoted to the store directory as real files. The darkroom rules are the referee: derivatives never touch the negative (checksummed), inversion is an involution (develop the …
Data & Pipelines
Round 18
Janko Marohnić
exit 0
bundle exec ruby examples/ascii_darkroom.rb
a real captured run
THE ASCII DARKROOM (every upload gem is a darkroom with worse lighting)
the negative that came in: ...and the developed print:
#################################### ::::::::::::::::::::::::::::::::::::
#################################### ::::::::::::::::::::::::::::::::::::
######################### ###### :::::::::::::::::::::::::@@@@@::::::
######################## ##### ::::::::::::::::::::::::@@@@@@@:::::
######################### ###### :::::::::::::::::::::::::@@@@@::::::
#################################### ::::::::::::::::::::::::::::::::::::
#############::::::::##############: :::::::::::::########::::::::::::::#
###########:::::::::::###########::: :::::::::::###########:::::::::::###
::########::::::::::::::########:::: ##::::::::##############::::::::####
:::::::::::::::::::::::::::::::::::: ####################################
:::::::::::::::::::::::::::::::::::: ####################################
:::::::::::::::::::::::::::::::::::: ####################################
:::::::::::::::::::::::::::::::::::: ####################################
:::::::::::::::::::::::::::::::::::: ####################################
the thumbnail derivative (18x7, from 36x14):
::::::::::::::::::
::::::::::::#@@-::
::::::::::::-++:::
:::::-+###+:::::-+
#====#######====##
##################
##################
darkroom rules: negative untouched (checksummed) - derivatives are
NEW files, never edits; develop(develop(x)) == x, proven, so the
print can always give you your negative back; the thumbnail is
really 18x7; and all three baths promoted to the store: yes.
the shape is every file-attachment pipeline ever shipped: one
original, held sacred; one expensive develop step; N cheap
derivative baths fanning out from the SAME print in parallel
(they share a dependency, not chemistry); and promotion to the
store as the atomic finale. the involution check is the one I
wish more pipelines had - a transform that can't round-trip is
a transform quietly eating data, and in a darkroom you find
that out when the wedding photos are already gone.
source
# frozen_string_literal: true # The ASCII Darkroom: a photo pipeline where the photos are made of # characters and the chemistry is arithmetic. One NEGATIVE comes in; # the enlarger develops it into a print; then three derivative baths # run in PARALLEL off the same developed print - high-contrast, # thumbnail, vignette - and everything is promoted to the store # directory as real files. The darkroom rules are the referee: # derivatives never touch the negative (checksummed), inversion is # an involution (develop the developed print and you get your # negative back, exactly), and the thumbnail had better actually be # smaller. Every upload gem is a darkroom with worse lighting. # # bundle exec ruby examples/ascii_darkroom.rb # # Runs offline; exits 1 if any darkroom rule is violated. require class="s">"bundler/setup" require class="s">"agentic" require class="s">"digest" require class="s">"tmpdir" Agentic.logger.level = class="y">:fatal SHADES = class="s">" .:-=+*#%@".chars.freeze # intensity 0..9 W = 36 H = 14 # The negative, exposed procedurally: a moon over mountains (inverted, as negatives are) NEGATIVE = H.times.map { |y| W.times.map { |x| moon = (Math.sqrt((x - 27)**2 + ((y - 3) * 2)**2) < 3.2) ? 9 : 0 ridge = (y > 7 + Math.sin(x / 3.5) * 2) ? 7 : 0 sky = [(H - y) / 3, 2].min 9 - [moon, ridge, sky].max # invert: it's a negative } }.freeze def show(pixels, indent: 4) pixels.map { |row| class="s">" " * indent + row.map { |v| SHADES[v.clamp(0, 9)] }.join }.join(class="s">"\n") end INVERT = ->(px) { px.map { |row| row.map { |v| 9 - v } } } CONTRAST = ->(px) { px.map { |row| row.map { |v| (v < 5) ? [v - 2, 0].max : [v + 2, 9].min } } } THUMBNAIL = ->(px) { (px.size / 2).times.map { |y| (px.first.size / 2).times.map { |x| (px[y * 2][x * 2] + px[y * 2][x * 2 + 1] + px[y * 2 + 1][x * 2] + px[y * 2 + 1][x * 2 + 1]) / 4 } } } VIGNETTE = ->(px) { h = px.size w = px.first.size px.each_with_index.map { |row, y| row.each_with_index.map { |v, x| edge = [x, y, w - 1 - x, h - 1 - y].min (edge < 3) ? [v - (3 - edge) * 2, 0].max : v } } } store = Dir.mktmpdir(class="s">"darkroom_store") negative_checksum = Digest:class="y">:SHA256.hexdigest(NEGATIVE.inspect) orchestrator = Agentic:class="y">:PlanOrchestrator.new(concurrency_limit: 3) develop = Agentic:class="y">:Task.new(description: class="s">"develop", agent_spec: {class="s">"name" => class="s">"enlarger", class="s">"instructions" => class="s">"develop"}) orchestrator.add_task(develop, agent: ->(_t) { INVERT.call(NEGATIVE) }) baths = {class="s">"contrast" => CONTRAST, class="s">"thumbnail" => THUMBNAIL, class="s">"vignette" => VIGNETTE} derivative_tasks = baths.to_h do |name, chemistry| task = Agentic:class="y">:Task.new(description: name, agent_spec: {class="s">"name" => name, class="s">"instructions" => class="s">"bathe"}) orchestrator.add_task(task, [develop], agent: ->(t) { derivative = chemistry.call(t.previous_output) File.write(File.join(store, class="s">"#{name}.txt"), show(derivative, indent: 0)) derivative }) [name, task] end result = orchestrator.execute_plan print_out = result.task_result(develop.id).output thumb = result.task_result(derivative_tasks[class="s">"thumbnail"].id).output puts class="s">"THE ASCII DARKROOM (every upload gem is a darkroom with worse lighting)" puts puts class="s">" the negative that came in: ...and the developed print:" NEGATIVE.each_index do |y| puts class="s">" #{NEGATIVE[y].map { |v| SHADES[v] }.join}#{show([print_out[y]], indent: 4)}" end puts puts class="s">" the thumbnail derivative (#{thumb.first.size}x#{thumb.size}, from #{W}x#{H}):" puts show(thumb) puts # --- the darkroom rules --------------------------------------------------------------- failures = [] failures << class="s">"the NEGATIVE was touched" unless Digest:class="y">:SHA256.hexdigest(NEGATIVE.inspect) == negative_checksum failures << class="s">"inversion is not an involution" unless INVERT.call(print_out) == NEGATIVE failures << class="s">"thumbnail dimensions wrong" unless thumb.size == H / 2 && thumb.first.size == W / 2 missing = baths.keys.reject { |name| File.exist?(File.join(store, class="s">"#{name}.txt")) } failures << class="s">"derivatives not promoted: #{missing}" if missing.any? failures << class="s">"a bath changed the print's dimensions" unless [result.task_result(derivative_tasks[class="s">"contrast"].id).output, result.task_result(derivative_tasks[class="s">"vignette"].id).output].all? { |d| d.size == H && d.first.size == W } puts class="s">" darkroom rules: negative untouched (checksummed) - derivatives are" puts class="s">" NEW files, never edits; develop(develop(x)) == x, proven, so the" puts class="s">" print can always give you your negative back; the thumbnail is" puts class="s">" really #{W / 2}x#{H / 2}; and all three baths promoted to the store: #{missing.empty? ? "yesclass="s">" : "NOclass="s">"}." puts puts class="s">" the shape is every file-attachment pipeline ever shipped: one" puts class="s">" original, held sacred; one expensive develop step; N cheap" puts class="s">" derivative baths fanning out from the SAME print in parallel" puts class="s">" (they share a dependency, not chemistry); and promotion to the" puts class="s">" store as the atomic finale. the involution check is the one I" puts class="s">" wish more pipelines had - a transform that can't round-trip is" puts class="s">" a transform quietly eating data, and in a darkroom you find" puts class="s">" that out when the wedding photos are already gone." exit(failures.empty? ? 0 : 1)