OutfitImageRenderer: Handle different layer sizes
Example: http://localhost:3000/outfits/new.png?species=18&color=40&pose=HAPPY_MASC&objects%5B%5D=78994 Before this change, the cape renders too big. Now, it renders correctly.
This commit is contained in:
parent
c241dc33b0
commit
dcbdf17e56
4 changed files with 66 additions and 5 deletions
|
|
@ -31,12 +31,19 @@ class OutfitImageRenderer
|
||||||
begin
|
begin
|
||||||
layer_image = Vips::Image.new_from_buffer(image_data, "")
|
layer_image = Vips::Image.new_from_buffer(image_data, "")
|
||||||
|
|
||||||
# Center the layer on the canvas
|
# Resize the layer to fit the canvas size
|
||||||
x_offset = (CANVAS_SIZE - layer_image.width) / 2
|
# All layer images are square, but may not be CANVAS_SIZE x CANVAS_SIZE
|
||||||
y_offset = (CANVAS_SIZE - layer_image.height) / 2
|
# We need to resize them to exactly CANVAS_SIZE x CANVAS_SIZE
|
||||||
|
if layer_image.width != CANVAS_SIZE || layer_image.height != CANVAS_SIZE
|
||||||
|
layer_image = layer_image.resize(
|
||||||
|
CANVAS_SIZE.to_f / layer_image.width,
|
||||||
|
vscale: CANVAS_SIZE.to_f / layer_image.height
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
# Composite this layer onto the canvas
|
# Composite this layer onto the canvas at (0, 0)
|
||||||
canvas = canvas.composite([layer_image], :over, x: x_offset, y: y_offset)
|
# No offset needed since the layer is now exactly canvas-sized
|
||||||
|
canvas = canvas.composite([layer_image], :over)
|
||||||
rescue Vips::Error => e
|
rescue Vips::Error => e
|
||||||
# Log and skip layers that fail to load/composite
|
# Log and skip layers that fail to load/composite
|
||||||
Rails.logger.warn "Failed to composite layer #{layer.id} (#{layer.image_url}): #{e.message}"
|
Rails.logger.warn "Failed to composite layer #{layer.id} (#{layer.image_url}): #{e.message}"
|
||||||
|
|
|
||||||
BIN
spec/fixtures/outfit_images/Blue Acara With Cape.png
vendored
Normal file
BIN
spec/fixtures/outfit_images/Blue Acara With Cape.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
spec/fixtures/outfit_images/Cape.png
vendored
Normal file
BIN
spec/fixtures/outfit_images/Cape.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
|
|
@ -182,5 +182,59 @@ RSpec.describe OutfitImageRenderer do
|
||||||
expect(result).to be_nil
|
expect(result).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "resizes all layers to 600x600 before compositing" do
|
||||||
|
# Load a 1200x1200 item layer (real-world case from Neopets)
|
||||||
|
item_1200_png = load_fixture_image('Cape.png')
|
||||||
|
acara_600_png = load_fixture_image('Blue Acara.png')
|
||||||
|
expected_composite_png = load_fixture_image('Blue Acara With Cape.png')
|
||||||
|
|
||||||
|
# Create assets
|
||||||
|
biology_asset = build_biology_asset(zones(:head), body_id: 1)
|
||||||
|
item_asset = build_item_asset(zones(:hat1), body_id: 1)
|
||||||
|
|
||||||
|
# Stub HTTP requests
|
||||||
|
stub_request(:get, biology_asset.image_url).
|
||||||
|
to_return(body: acara_600_png, status: 200)
|
||||||
|
stub_request(:get, item_asset.image_url).
|
||||||
|
to_return(body: item_1200_png, status: 200)
|
||||||
|
|
||||||
|
# Build outfit
|
||||||
|
pet_state = build_pet_state(@pet_type, swf_assets: [biology_asset])
|
||||||
|
item = build_item("Test Item", swf_assets: [item_asset])
|
||||||
|
outfit = Outfit.new(
|
||||||
|
pet_state: pet_state,
|
||||||
|
worn_items: [item]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Render
|
||||||
|
renderer = OutfitImageRenderer.new(outfit)
|
||||||
|
result = renderer.render
|
||||||
|
|
||||||
|
# Verify we got valid PNG data
|
||||||
|
expect(result).not_to be_nil
|
||||||
|
expect(result).to be_a(String)
|
||||||
|
expect(result[0..7]).to eq("\x89PNG\r\n\x1A\n".b)
|
||||||
|
|
||||||
|
# Verify the result is exactly 600x600
|
||||||
|
result_image = Vips::Image.new_from_buffer(result, "")
|
||||||
|
expect(result_image.width).to eq(600)
|
||||||
|
expect(result_image.height).to eq(600)
|
||||||
|
|
||||||
|
# Verify the composite matches the expected image pixel-perfectly
|
||||||
|
expected_image = Vips::Image.new_from_buffer(expected_composite_png, "")
|
||||||
|
|
||||||
|
# Calculate the absolute difference between images
|
||||||
|
diff = (result_image - expected_image).abs
|
||||||
|
max_diff = diff.max
|
||||||
|
|
||||||
|
# Allow a small tolerance for minor encoding/compositing differences
|
||||||
|
tolerance = 2
|
||||||
|
if max_diff > tolerance
|
||||||
|
debug_path = Rails.root.join('tmp', 'test_render_1200_result.png')
|
||||||
|
result_image.write_to_file(debug_path.to_s)
|
||||||
|
fail "Images should match within tolerance of #{tolerance}, but found max difference of #{max_diff}. Actual output saved to #{debug_path}"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue