Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/gallery/assets/png-exr-alpha-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 84 additions & 27 deletions docs/gallery/png-exr-alpha/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,12 @@ <h2>Source</h2>
Columns = rising alpha; top rows = dark mid-tones that PNG clamps to
white; lower rows = primaries that survive. Left panel bakes the
closed-form mangling; right panel shows authored straight RGBA.

A dark bezel is painted into the texture so panel edges read as framed
objects in the studio still (render-only — checks use fill_pattern).
&quot;&quot;&quot;</span>
gw, gh = <span class="n">256</span>, <span class="n">192</span>
border = <span class="n">10</span>
img = bpy.data.images.new(name, gw, gh, alpha=<span class="k">True</span>, float_buffer=<span class="k">True</span>)
img.colorspace_settings.name = <span class="s">&quot;Non-Color&quot;</span>
px = [<span class="n">0.0</span>] * (gw * gh * <span class="n">4</span>)
Expand All @@ -567,25 +571,32 @@ <h2>Source</h2>
(<span class="n">0.2</span>, <span class="n">0.35</span>, <span class="n">1.0</span>), <span class="c"># blue-leaning; G/B still clamp at low a → wash</span>
]
alphas = [<span class="n">1</span> / <span class="n">255</span>, <span class="n">8</span> / <span class="n">255</span>, <span class="n">32</span> / <span class="n">255</span>, <span class="n">64</span> / <span class="n">255</span>, <span class="n">128</span> / <span class="n">255</span>, <span class="n">1.0</span>]
col_w = gw // n_cols
row_h = gh // len(row_colors)
iw, ih = gw - <span class="n">2</span> * border, gh - <span class="n">2</span> * border
col_w = max(<span class="n">1</span>, iw // n_cols)
row_h = max(<span class="n">1</span>, ih // len(row_colors))
bezel = (<span class="n">0.04</span>, <span class="n">0.042</span>, <span class="n">0.048</span>)
<span class="k">for</span> y <span class="k">in</span> range(gh):
ri = min(y // row_h, len(row_colors) - <span class="n">1</span>)
rgb = row_colors[ri]
<span class="k">for</span> x <span class="k">in</span> range(gw):
ci = min(x // col_w, n_cols - <span class="n">1</span>)
i = (y * gw + x) * <span class="n">4</span>
<span class="k">if</span> x &lt; border <span class="k">or</span> y &lt; border <span class="k">or</span> x &gt;= gw - border <span class="k">or</span> y &gt;= gh - border:
px[i : i + <span class="n">4</span>] = [bezel[<span class="n">0</span>], bezel[<span class="n">1</span>], bezel[<span class="n">2</span>], <span class="n">1.0</span>]
<span class="k">continue</span>
xi, yi = x - border, y - border
ri = min(yi // row_h, len(row_colors) - <span class="n">1</span>)
ci = min(xi // col_w, n_cols - <span class="n">1</span>)
rgb = row_colors[ri]
a = alphas[ci]
<span class="k">if</span> mangled:
r, g, b, _a = expected_float_png_rgba(*rgb, a)
<span class="k">else</span>:
r, g, b = rgb
i = (y * gw + x) * <span class="n">4</span>
px[i : i + <span class="n">4</span>] = [r, g, b, <span class="n">1.0</span>]
img.pixels.foreach_set(px)
<span class="k">return</span> img


<span class="k">def</span> easel_plane(name, image, loc, rot_z_deg):
<span class="s">&quot;&quot;&quot;Single-plane easel + short stand — no dual-surface z-fight.&quot;&quot;&quot;</span>
me = bpy.data.meshes.new(name)
bm = bmesh.new()
<span class="k">try</span>:
Expand All @@ -610,19 +621,46 @@ <h2>Source</h2>
tex = nodes.new(<span class="s">&quot;ShaderNodeTexImage&quot;</span>)
tex.image = image
tex.interpolation = <span class="s">&quot;Closest&quot;</span>
links.new(tex.outputs[<span class="s">&quot;Color&quot;</span>], bsdf.inputs[<span class="s">&quot;Base Color&quot;</span>])
links.new(tex.outputs[<span class="s">&quot;Color&quot;</span>], bsdf.inputs[<span class="s">&quot;Emission Color&quot;</span>])
bsdf.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.0</span>)
bsdf.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">1.0</span>
specular_off(bsdf)
bsdf.inputs[<span class="s">&quot;Emission Strength&quot;</span>].default_value = <span class="n">0.95</span>
<span class="c"># Faint emission so clamps read under a dim stage without washing the wall.</span>
bsdf.inputs[<span class="s">&quot;Emission Strength&quot;</span>].default_value = <span class="n">0.22</span>
me.materials.append(mat)

obj = bpy.data.objects.new(name, me)
obj.location = loc
obj.rotation_euler = (math.radians(<span class="n">62</span>), <span class="n">0.0</span>, math.radians(rot_z_deg))
obj.scale = (<span class="n">1.15</span>, <span class="n">1.15</span>, <span class="n">1.15</span>)
bpy.context.collection.objects.link(obj)
<span class="k">return</span> obj
root = bpy.data.objects.new(name, <span class="k">None</span>)
root.location = loc
root.rotation_euler = (math.radians(<span class="n">62</span>), <span class="n">0.0</span>, math.radians(rot_z_deg))
root.scale = (<span class="n">0.88</span>, <span class="n">0.88</span>, <span class="n">0.88</span>)
bpy.context.collection.objects.link(root)

face = bpy.data.objects.new(name + <span class="s">&quot;Face&quot;</span>, me)
bpy.context.collection.objects.link(face)
face.parent = root

stand_me = bpy.data.meshes.new(name + <span class="s">&quot;Stand&quot;</span>)
bm = bmesh.new()
<span class="k">try</span>:
bmesh.ops.create_cube(bm, size=<span class="n">1.0</span>)
bm.to_mesh(stand_me)
<span class="k">finally</span>:
bm.free()
smat = bpy.data.materials.new(name + <span class="s">&quot;StandMat&quot;</span>)
smat.use_nodes = <span class="k">True</span>
sb = smat.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
sb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.045</span>, <span class="n">0.048</span>, <span class="n">0.055</span>, <span class="n">1.0</span>)
sb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.45</span>
<span class="k">if</span> <span class="s">&quot;Metallic&quot;</span> <span class="k">in</span> sb.inputs:
sb.inputs[<span class="s">&quot;Metallic&quot;</span>].default_value = <span class="n">0.3</span>
stand_me.materials.append(smat)
stand = bpy.data.objects.new(name + <span class="s">&quot;Stand&quot;</span>, stand_me)
<span class="c"># Fully below the card face — never intersects the evidence plane.</span>
stand.scale = (<span class="n">0.06</span>, <span class="n">0.06</span>, <span class="n">0.28</span>)
stand.location = (<span class="n">0.0</span>, <span class="n">0.04</span>, -<span class="n">0.88</span>)
bpy.context.collection.objects.link(stand)
stand.parent = root
<span class="k">return</span> root


<span class="k">def</span> render_still(path, engine):
Expand All @@ -631,8 +669,27 @@ <h2>Source</h2>
png_card = make_gallery_card(<span class="s">&quot;PngMangled&quot;</span>, mangled=<span class="k">True</span>)
exr_card = make_gallery_card(<span class="s">&quot;ExrClean&quot;</span>, mangled=<span class="k">False</span>)

easel_plane(<span class="s">&quot;PngPanel&quot;</span>, png_card, (-<span class="n">1.22</span>, <span class="n">0.0</span>, <span class="n">1.18</span>), <span class="n">10</span>)
easel_plane(<span class="s">&quot;ExrPanel&quot;</span>, exr_card, (<span class="n">1.22</span>, <span class="n">0.0</span>, <span class="n">1.18</span>), -<span class="n">10</span>)
<span class="c"># Mounted easels on a shared dark table — continuous base, no V-tear shadow.</span>
easel_plane(<span class="s">&quot;PngPanel&quot;</span>, png_card, (-<span class="n">1.15</span>, <span class="n">0.1</span>, <span class="n">1.28</span>), <span class="n">5</span>)
easel_plane(<span class="s">&quot;ExrPanel&quot;</span>, exr_card, (<span class="n">1.15</span>, <span class="n">0.1</span>, <span class="n">1.28</span>), -<span class="n">5</span>)

table_me = bpy.data.meshes.new(<span class="s">&quot;Table&quot;</span>)
bm = bmesh.new()
<span class="k">try</span>:
bmesh.ops.create_cube(bm, size=<span class="n">1.0</span>)
bm.to_mesh(table_me)
<span class="k">finally</span>:
bm.free()
tmat = bpy.data.materials.new(<span class="s">&quot;TableMat&quot;</span>)
tmat.use_nodes = <span class="k">True</span>
tb = tmat.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
tb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.022</span>, <span class="n">0.024</span>, <span class="n">0.028</span>, <span class="n">1.0</span>)
tb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.6</span>
table_me.materials.append(tmat)
table = bpy.data.objects.new(<span class="s">&quot;Table&quot;</span>, table_me)
table.scale = (<span class="n">1.5</span>, <span class="n">0.55</span>, <span class="n">0.08</span>)
table.location = (<span class="n">0.0</span>, <span class="n">0.25</span>, <span class="n">0.28</span>)
scene.collection.objects.link(table)

floor_me = bpy.data.meshes.new(<span class="s">&quot;Floor&quot;</span>)
bm = bmesh.new()
Expand All @@ -644,20 +701,20 @@ <h2>Source</h2>
fmat = bpy.data.materials.new(<span class="s">&quot;Studio&quot;</span>)
fmat.use_nodes = <span class="k">True</span>
fb = fmat.node_tree.nodes[<span class="s">&quot;Principled BSDF&quot;</span>]
fb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.03</span>, <span class="n">0.032</span>, <span class="n">0.037</span>, <span class="n">1.0</span>)
fb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.7</span>
fb.inputs[<span class="s">&quot;Base Color&quot;</span>].default_value = (<span class="n">0.02</span>, <span class="n">0.021</span>, <span class="n">0.025</span>, <span class="n">1.0</span>)
fb.inputs[<span class="s">&quot;Roughness&quot;</span>].default_value = <span class="n">0.8</span>
floor_me.materials.append(fmat)
floor = bpy.data.objects.new(<span class="s">&quot;Floor&quot;</span>, floor_me)
scene.collection.objects.link(floor)
wall = bpy.data.objects.new(<span class="s">&quot;Wall&quot;</span>, floor_me.copy())
wall.location = (<span class="n">0.0</span>, <span class="n">6.8</span>, <span class="n">0.0</span>)
wall.location = (<span class="n">0.0</span>, <span class="n">8.8</span>, <span class="n">0.0</span>)
wall.rotation_euler = (math.radians(<span class="n">90</span>), <span class="n">0.0</span>, <span class="n">0.0</span>)
scene.collection.objects.link(wall)

world = bpy.data.worlds.new(<span class="s">&quot;World&quot;</span>)
world.use_nodes = <span class="k">True</span>
world.node_tree.nodes[<span class="s">&quot;Background&quot;</span>].inputs[<span class="s">&quot;Color&quot;</span>].default_value = (
<span class="n">0.02</span>, <span class="n">0.021</span>, <span class="n">0.025</span>, <span class="n">1.0</span>,
<span class="n">0.012</span>, <span class="n">0.013</span>, <span class="n">0.015</span>, <span class="n">1.0</span>,
)
scene.world = world

Expand All @@ -671,19 +728,19 @@ <h2>Source</h2>
ob.rotation_euler = tuple(math.radians(a) <span class="k">for</span> a <span class="k">in</span> rot)
scene.collection.objects.link(ob)

light(<span class="s">&quot;Key&quot;</span>, (-<span class="n">3.8</span>, -<span class="n">4.8</span>, <span class="n">5.8</span>), <span class="n">360.0</span>, <span class="n">4.5</span>, (<span class="n">1.0</span>, <span class="n">0.96</span>, <span class="n">0.9</span>), (<span class="n">52</span>, <span class="n">0</span>, -<span class="n">32</span>))
light(<span class="s">&quot;Fill&quot;</span>, (<span class="n">4.8</span>, -<span class="n">2.8</span>, <span class="n">1.6</span>), <span class="n">70.0</span>, <span class="n">9.0</span>, (<span class="n">0.75</span>, <span class="n">0.85</span>, <span class="n">1.0</span>), (<span class="n">72</span>, <span class="n">0</span>, <span class="n">52</span>))
light(<span class="s">&quot;Rim&quot;</span>, (<span class="n">0.2</span>, <span class="n">4.0</span>, <span class="n">2.8</span>), <span class="n">220.0</span>, <span class="n">3.2</span>, (<span class="n">0.6</span>, <span class="n">0.78</span>, <span class="n">1.0</span>), (-<span class="n">62</span>, <span class="n">0</span>, <span class="n">178</span>))
light(<span class="s">&quot;Wedge&quot;</span>, (<span class="n">0.5</span>, <span class="n">5.2</span>, <span class="n">2.8</span>), <span class="n">520.0</span>, <span class="n">7.0</span>, (<span class="n">1.0</span>, <span class="n">0.72</span>, <span class="n">0.42</span>), (-<span class="n">78</span>, <span class="n">0</span>, <span class="n">180</span>))
light(<span class="s">&quot;Key&quot;</span>, (-<span class="n">3.8</span>, -<span class="n">4.8</span>, <span class="n">5.8</span>), <span class="n">220.0</span>, <span class="n">4.5</span>, (<span class="n">1.0</span>, <span class="n">0.96</span>, <span class="n">0.9</span>), (<span class="n">52</span>, <span class="n">0</span>, -<span class="n">32</span>))
light(<span class="s">&quot;Fill&quot;</span>, (<span class="n">4.8</span>, -<span class="n">2.8</span>, <span class="n">1.6</span>), <span class="n">40.0</span>, <span class="n">9.0</span>, (<span class="n">0.75</span>, <span class="n">0.85</span>, <span class="n">1.0</span>), (<span class="n">72</span>, <span class="n">0</span>, <span class="n">52</span>))
light(<span class="s">&quot;Rim&quot;</span>, (<span class="n">0.2</span>, <span class="n">4.8</span>, <span class="n">2.8</span>), <span class="n">120.0</span>, <span class="n">3.2</span>, (<span class="n">0.6</span>, <span class="n">0.78</span>, <span class="n">1.0</span>), (-<span class="n">62</span>, <span class="n">0</span>, <span class="n">178</span>))
light(<span class="s">&quot;Wedge&quot;</span>, (<span class="n">0.5</span>, <span class="n">6.5</span>, <span class="n">2.5</span>), <span class="n">450.0</span>, <span class="n">7.0</span>, (<span class="n">1.0</span>, <span class="n">0.72</span>, <span class="n">0.42</span>), (-<span class="n">78</span>, <span class="n">0</span>, <span class="n">180</span>))

aim = bpy.data.objects.new(<span class="s">&quot;Aim&quot;</span>, <span class="k">None</span>)
aim.location = (<span class="n">0.0</span>, <span class="n">0.0</span>, <span class="n">1.1</span>)
aim.location = (<span class="n">0.0</span>, <span class="n">0.1</span>, <span class="n">1.15</span>)
scene.collection.objects.link(aim)

cam_data = bpy.data.cameras.new(<span class="s">&quot;Cam&quot;</span>)
cam_data.lens = <span class="n">45.0</span>
cam_data.lens = <span class="n">50.0</span>
cam = bpy.data.objects.new(<span class="s">&quot;Cam&quot;</span>, cam_data)
cam.location = (<span class="n">0.0</span>, -<span class="n">5.4</span>, <span class="n">2.2</span>)
cam.location = (<span class="n">0.0</span>, -<span class="n">6.0</span>, <span class="n">2.15</span>)
con = cam.constraints.new(<span class="s">&quot;TRACK_TO&quot;</span>)
con.target = aim
con.track_axis = <span class="s">&quot;TRACK_NEGATIVE_Z&quot;</span>
Expand Down
Loading
Loading