From a65e43d7e1e262f73725d7ba9c477c2d2dd5c859 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 17 Oct 2025 22:46:32 -0700 Subject: [PATCH 1/2] feat: add Floyd-Steinberg error diffusion dithering Implements Floyd-Steinberg error diffusion algorithm for the draw_with_dither tool. The algorithm creates gradients by distributing quantization errors to neighboring pixels using the standard 7/16, 5/16, 3/16, 1/16 weight pattern. - Add floyd_steinberg pattern to draw_with_dither (16 patterns total) - Implement gradient generation and error propagation in Lua - Use Euclidean distance for color selection instead of luminance threshold - Add test verifying gradient output by reading back pixels - Add side-by-side example comparing Bayer 4x4 vs Floyd-Steinberg Fixes #5 --- examples/client/main.go | 38 ++++-- pkg/aseprite/lua_drawing.go | 238 ++++++++++++++++++++++++++++++++++++ pkg/tools/dithering.go | 7 +- pkg/tools/dithering_test.go | 134 ++++++++++++++++++++ 4 files changed, 406 insertions(+), 11 deletions(-) diff --git a/examples/client/main.go b/examples/client/main.go index 78625e8..597fbda 100644 --- a/examples/client/main.go +++ b/examples/client/main.go @@ -246,9 +246,9 @@ func createAnimatedSprite(ctx context.Context, session *mcp.ClientSession, logge // Step 9: Demonstrate dithering (create a new sprite with gradient) logger.Information("") - logger.Information("Step 9: Creating sprite with dithered gradient...") + logger.Information("Step 9: Creating sprite for dithering comparison (Bayer vs Floyd-Steinberg)...") ditherResp, err := callTool(ctx, session, "create_canvas", map[string]any{ - "width": 64, + "width": 128, "height": 64, "color_mode": "rgb", }) @@ -264,9 +264,9 @@ func createAnimatedSprite(ctx context.Context, session *mcp.ClientSession, logge ditherSprite := ditherResult.FilePath logger.Information(" Created: {DitherSprite}", ditherSprite) - // Apply dithering with Bayer 4x4 pattern + // Apply Bayer 4x4 dithering to left half logger.Information("") - logger.Information("Step 10: Applying Bayer 4x4 dithering pattern...") + logger.Information("Step 10: Applying Bayer 4x4 pattern (left half)...") if _, err := callTool(ctx, session, "draw_with_dither", map[string]any{ "sprite_path": ditherSprite, "layer_name": "Layer 1", @@ -282,14 +282,35 @@ func createAnimatedSprite(ctx context.Context, session *mcp.ClientSession, logge "pattern": "bayer_4x4", "density": 0.5, }); err != nil { - return fmt.Errorf("draw_with_dither failed: %w", err) + return fmt.Errorf("draw_with_dither (bayer) failed: %w", err) + } + logger.Information(" Bayer 4x4 pattern applied (ordered dithering)") + + // Apply Floyd-Steinberg dithering to right half + logger.Information(" Applying Floyd-Steinberg pattern (right half)...") + if _, err := callTool(ctx, session, "draw_with_dither", map[string]any{ + "sprite_path": ditherSprite, + "layer_name": "Layer 1", + "frame_number": 1, + "region": map[string]any{ + "x": 64, + "y": 0, + "width": 64, + "height": 64, + }, + "color1": "#001F3F", + "color2": "#7FDBFF", + "pattern": "floyd_steinberg", + "density": 0.5, + }); err != nil { + return fmt.Errorf("draw_with_dither (floyd-steinberg) failed: %w", err) } - logger.Information(" Dithering applied successfully") + logger.Information(" Floyd-Steinberg pattern applied (error diffusion)") // Export dithered sprite logger.Information("") - logger.Information("Step 11: Exporting dithered gradient...") - ditherPngPath := filepath.Join(outputDir, "dithered-gradient.png") + logger.Information("Step 11: Exporting dithering comparison...") + ditherPngPath := filepath.Join(outputDir, "dithering-comparison.png") if _, err := callTool(ctx, session, "export_sprite", map[string]any{ "sprite_path": ditherSprite, "output_path": ditherPngPath, @@ -299,6 +320,7 @@ func createAnimatedSprite(ctx context.Context, session *mcp.ClientSession, logge return fmt.Errorf("export_sprite dither failed: %w", err) } logger.Information(" Exported: {DitherPng}", ditherPngPath) + logger.Information(" Left: Bayer 4x4 (ordered pattern) | Right: Floyd-Steinberg (error diffusion)") // Step 12: Analyze palette harmonies logger.Information("") diff --git a/pkg/aseprite/lua_drawing.go b/pkg/aseprite/lua_drawing.go index fea94e1..9dbbf72 100644 --- a/pkg/aseprite/lua_drawing.go +++ b/pkg/aseprite/lua_drawing.go @@ -694,6 +694,9 @@ local matrixSize = 4` {1, 0, 1, 0} } local matrixSize = 4` + case "floyd_steinberg": + // Floyd-Steinberg uses error diffusion instead of matrix patterns + return generateFloydSteinbergLua(escapedLayerName, frameNumber, x, y, width, height, c1, c2, density) default: return fmt.Sprintf(`error("Unknown dithering pattern: %s")`, pattern) } @@ -809,3 +812,238 @@ print("Dithering applied successfully")`, height, width, x, y) } + +// generateFloydSteinbergLua generates Lua script for Floyd-Steinberg error diffusion dithering. +// +// Floyd-Steinberg is an error diffusion algorithm that produces high-quality dithering +// by propagating quantization error to neighboring pixels. The error distribution pattern is: +// +// X 7/16 +// 3/16 5/16 1/16 +// +// where X is the current pixel being processed. +// +// Parameters match DrawWithDither but the density parameter is interpreted as a threshold: +// pixels with luminance < density*255 map to color2, otherwise color1. +func generateFloydSteinbergLua(layerName string, frameNumber int, x, y, width, height int, c1, c2 Color, density float64) string { + return fmt.Sprintf(`local spr = app.activeSprite +if not spr then + error("No active sprite") +end + +-- Find layer +local layer = nil +for _, l in ipairs(spr.layers) do + if l.name == "%s" then + layer = l + break + end +end + +if not layer then + error("Layer not found: %s") +end + +-- Get frame +local frame = spr.frames[%d] +if not frame then + error("Frame not found: %d") +end + +-- Get or create cel +local cel = layer:cel(frame) +if not cel then + cel = spr:newCel(layer, frame) +end + +-- Create or get image +local img = cel.image +if not img then + img = Image(spr.width, spr.height, spr.colorMode) + cel.image = img +end + +-- Helper: Find nearest palette index for given RGBA color +local function findNearestPaletteIndex(r, g, b, a) + local palette = spr.palettes[1] + if not palette or #palette == 0 then + return 0 + end + + local minDist = math.huge + local nearestIndex = 0 + + for i = 0, #palette - 1 do + local palColor = palette:getColor(i) + local dr = r - palColor.red + local dg = g - palColor.green + local db = b - palColor.blue + local da = a - palColor.alpha + local dist = dr*dr + dg*dg + db*db + da*da + + if dist < minDist then + minDist = dist + nearestIndex = i + end + end + + return nearestIndex +end + +-- Define colors based on color mode +local color1, color2 +local color1_r, color1_g, color1_b, color1_a = %d, %d, %d, %d +local color2_r, color2_g, color2_b, color2_a = %d, %d, %d, %d + +if spr.colorMode == ColorMode.INDEXED then + -- In indexed mode, img:drawPixel expects palette indices + color1 = findNearestPaletteIndex(color1_r, color1_g, color1_b, color1_a) + color2 = findNearestPaletteIndex(color2_r, color2_g, color2_b, color2_a) +else + -- In RGB/Grayscale mode, use pixel color values + color1 = app.pixelColor.rgba(color1_r, color1_g, color1_b, color1_a) + color2 = app.pixelColor.rgba(color2_r, color2_g, color2_b, color2_a) +end + +-- Floyd-Steinberg error diffusion +app.transaction(function() + -- Create error buffer (width+2 to handle edges, 2 rows for current and next) + local errors = {} + for row = 0, 1 do + errors[row] = {} + for col = 0, %d + 1 do + errors[row][col] = {r=0, g=0, b=0} + end + end + + for py = 0, %d - 1 do + -- Swap error buffers for next row + if py > 0 then + errors[0], errors[1] = errors[1], errors[0] + -- Clear the new "next row" buffer + for col = 0, %d + 1 do + errors[1][col] = {r=0, g=0, b=0} + end + end + + for px = 0, %d - 1 do + -- Calculate gradient position (0.0 to 1.0 across width) + local gradient_pos = px / (%d - 1) + + -- Calculate ideal gradient color at this position + local ideal_r = color1_r + (color2_r - color1_r) * gradient_pos + local ideal_g = color1_g + (color2_g - color1_g) * gradient_pos + local ideal_b = color1_b + (color2_b - color1_b) * gradient_pos + + -- Add accumulated error + local err = errors[0][px + 1] + local new_r = math.max(0, math.min(255, ideal_r + err.r)) + local new_g = math.max(0, math.min(255, ideal_g + err.g)) + local new_b = math.max(0, math.min(255, ideal_b + err.b)) + + -- Calculate color with accumulated error + local targetColor + if spr.colorMode == ColorMode.INDEXED then + -- Choose nearest color by Euclidean distance + local dist1 = (new_r - color1_r)^2 + (new_g - color1_g)^2 + (new_b - color1_b)^2 + local dist2 = (new_r - color2_r)^2 + (new_g - color2_g)^2 + (new_b - color2_b)^2 + + targetColor = (dist1 < dist2) and color1 or color2 + + -- Calculate error in RGB space + local actual_r, actual_g, actual_b + if targetColor == color1 then + actual_r, actual_g, actual_b = color1_r, color1_g, color1_b + else + actual_r, actual_g, actual_b = color2_r, color2_g, color2_b + end + + local err_r = new_r - actual_r + local err_g = new_g - actual_g + local err_b = new_b - actual_b + + -- Distribute error to neighbors (Floyd-Steinberg weights) + if px < %d - 1 then + errors[0][px + 2].r = errors[0][px + 2].r + err_r * 7/16 + errors[0][px + 2].g = errors[0][px + 2].g + err_g * 7/16 + errors[0][px + 2].b = errors[0][px + 2].b + err_b * 7/16 + end + if py < %d - 1 then + if px > 0 then + errors[1][px].r = errors[1][px].r + err_r * 3/16 + errors[1][px].g = errors[1][px].g + err_g * 3/16 + errors[1][px].b = errors[1][px].b + err_b * 3/16 + end + errors[1][px + 1].r = errors[1][px + 1].r + err_r * 5/16 + errors[1][px + 1].g = errors[1][px + 1].g + err_g * 5/16 + errors[1][px + 1].b = errors[1][px + 1].b + err_b * 5/16 + if px < %d - 1 then + errors[1][px + 2].r = errors[1][px + 2].r + err_r * 1/16 + errors[1][px + 2].g = errors[1][px + 2].g + err_g * 1/16 + errors[1][px + 2].b = errors[1][px + 2].b + err_b * 1/16 + end + end + else + -- RGB mode - choose nearest color by Euclidean distance + local dist1 = (new_r - color1_r)^2 + (new_g - color1_g)^2 + (new_b - color1_b)^2 + local dist2 = (new_r - color2_r)^2 + (new_g - color2_g)^2 + (new_b - color2_b)^2 + + targetColor = (dist1 < dist2) and color1 or color2 + + -- Calculate error (difference between gradient+error and quantized color) + local actual_r = app.pixelColor.rgbaR(targetColor) + local actual_g = app.pixelColor.rgbaG(targetColor) + local actual_b = app.pixelColor.rgbaB(targetColor) + + local err_r = new_r - actual_r + local err_g = new_g - actual_g + local err_b = new_b - actual_b + + -- Distribute error to neighbors + if px < %d - 1 then + errors[0][px + 2].r = errors[0][px + 2].r + err_r * 7/16 + errors[0][px + 2].g = errors[0][px + 2].g + err_g * 7/16 + errors[0][px + 2].b = errors[0][px + 2].b + err_b * 7/16 + end + if py < %d - 1 then + if px > 0 then + errors[1][px].r = errors[1][px].r + err_r * 3/16 + errors[1][px].g = errors[1][px].g + err_g * 3/16 + errors[1][px].b = errors[1][px].b + err_b * 3/16 + end + errors[1][px + 1].r = errors[1][px + 1].r + err_r * 5/16 + errors[1][px + 1].g = errors[1][px + 1].g + err_g * 5/16 + errors[1][px + 1].b = errors[1][px + 1].b + err_b * 5/16 + if px < %d - 1 then + errors[1][px + 2].r = errors[1][px + 2].r + err_r * 1/16 + errors[1][px + 2].g = errors[1][px + 2].g + err_g * 1/16 + errors[1][px + 2].b = errors[1][px + 2].b + err_b * 1/16 + end + end + end + + img:drawPixel(%d + px, %d + py, targetColor) + end + end +end) + +spr:saveAs(spr.filename) +print("Dithering applied successfully")`, + layerName, layerName, + frameNumber, frameNumber, + c1.R, c1.G, c1.B, c1.A, + c2.R, c2.G, c2.B, c2.A, + width, // line 914: error buffer width + height, // line 919: py loop + width, // line 924: clear buffer width + width, // line 929: px loop + width, // line 931: gradient calculation + width, // line 966: right neighbor check + height, // line 971: bottom neighbor check + width, // line 980: bottom-right check + width, // line 1006: right neighbor check (RGB) + height, // line 1011: bottom neighbor check (RGB) + width, // line 1020: bottom-right check (RGB) + x, // line 1028: x coordinate + y) // line 1028: y coordinate +} diff --git a/pkg/tools/dithering.go b/pkg/tools/dithering.go index cab1439..5da6a4b 100644 --- a/pkg/tools/dithering.go +++ b/pkg/tools/dithering.go @@ -18,7 +18,7 @@ type DrawWithDitherInput struct { Region RegionInput `json:"region" jsonschema:"Rectangular region to fill with dithering"` Color1 string `json:"color1" jsonschema:"First color (hex #RRGGBB or #RRGGBBAA)"` Color2 string `json:"color2" jsonschema:"Second color (hex #RRGGBB or #RRGGBBAA)"` - Pattern string `json:"pattern" jsonschema:"Dithering pattern: bayer_2x2|bayer_4x4|bayer_8x8|checkerboard|grass|water|stone|cloud|brick|dots|diagonal|cross|noise|horizontal_lines|vertical_lines"` + Pattern string `json:"pattern" jsonschema:"Dithering pattern: bayer_2x2|bayer_4x4|bayer_8x8|checkerboard|floyd_steinberg|grass|water|stone|cloud|brick|dots|diagonal|cross|noise|horizontal_lines|vertical_lines"` Density float64 `json:"density,omitempty" jsonschema:"Ratio of color1 to color2 (0.0-1.0, default: 0.5)"` } @@ -37,7 +37,7 @@ func RegisterDitheringTools(server *mcp.Server, client *aseprite.Client, gen *as server, &mcp.Tool{ Name: "draw_with_dither", - Description: "Fill a region with a dithering pattern to create smooth gradients and textures. Supports 15 patterns: Bayer matrix (bayer_2x2, bayer_4x4, bayer_8x8) for ordered dithering, checkerboard for 50/50 blends, and texture patterns (grass, water, stone, cloud, brick, dots, diagonal, cross, noise, horizontal_lines, vertical_lines) for organic effects. Use density parameter to control the ratio of color1 to color2 (0.0 = all color1, 1.0 = all color2, 0.5 = even mix). Essential for professional pixel art gradients and textures.", + Description: "Fill a region with a dithering pattern to create smooth gradients and textures. Supports 16 patterns: Bayer matrix (bayer_2x2, bayer_4x4, bayer_8x8) for ordered dithering, Floyd-Steinberg error diffusion (floyd_steinberg) for high-quality gradients, checkerboard for 50/50 blends, and texture patterns (grass, water, stone, cloud, brick, dots, diagonal, cross, noise, horizontal_lines, vertical_lines) for organic effects. Use density parameter to control the ratio of color1 to color2 (0.0 = all color1, 1.0 = all color2, 0.5 = even mix). Essential for professional pixel art gradients and textures.", }, maybeWrapWithTiming("draw_with_dither", logger, cfg.EnableTiming, func(ctx context.Context, req *mcp.CallToolRequest, input DrawWithDitherInput) (*mcp.CallToolResult, *struct{ Success bool }, error) { opLogger := logger.WithContext(ctx) @@ -79,9 +79,10 @@ func RegisterDitheringTools(server *mcp.Server, client *aseprite.Client, gen *as "noise": true, "horizontal_lines": true, "vertical_lines": true, + "floyd_steinberg": true, } if !validPatterns[input.Pattern] { - return nil, nil, fmt.Errorf("invalid pattern: %s (must be one of: bayer_2x2, bayer_4x4, bayer_8x8, checkerboard, grass, water, stone, cloud, brick, dots, diagonal, cross, noise, horizontal_lines, vertical_lines)", input.Pattern) + return nil, nil, fmt.Errorf("invalid pattern: %s (must be one of: bayer_2x2, bayer_4x4, bayer_8x8, checkerboard, grass, water, stone, cloud, brick, dots, diagonal, cross, noise, horizontal_lines, vertical_lines, floyd_steinberg)", input.Pattern) } // Validate colors (basic hex format check) diff --git a/pkg/tools/dithering_test.go b/pkg/tools/dithering_test.go index ebb08ea..f440769 100644 --- a/pkg/tools/dithering_test.go +++ b/pkg/tools/dithering_test.go @@ -32,6 +32,8 @@ func createDitheringTestSession(t *testing.T) (*mcp.Server, *mcp.ClientSession, RegisterDitheringTools(server, client, gen, cfg, logger) // Also register canvas tools for setup RegisterCanvasTools(server, client, gen, cfg, logger) + // Register inspection tools for pixel verification + RegisterInspectionTools(server, client, gen, cfg, logger) serverTransport, clientTransport := mcp.NewInMemoryTransports() _, err := server.Connect(context.Background(), serverTransport, nil) @@ -90,6 +92,7 @@ func TestDrawWithDither_ViaMCP(t *testing.T) { {"Noise texture", "noise", 0.5}, {"Horizontal lines", "horizontal_lines", 0.5}, {"Vertical lines", "vertical_lines", 0.5}, + {"Floyd-Steinberg", "floyd_steinberg", 0.5}, } for _, tt := range tests { @@ -174,6 +177,137 @@ func TestDrawWithDitherDensity_ViaMCP(t *testing.T) { } } +func TestFloydSteinbergGradient_ViaMCP(t *testing.T) { + _, session, _ := createDitheringTestSession(t) + defer session.Close() + + // Create a 64x64 sprite + createResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "create_canvas", + Arguments: map[string]any{ + "width": 64, + "height": 64, + "color_mode": "rgb", + }, + }) + require.NoError(t, err) + require.False(t, createResult.IsError) + + var createOutput struct { + FilePath string `json:"file_path"` + } + json.Unmarshal([]byte(createResult.Content[0].(*mcp.TextContent).Text), &createOutput) + defer os.Remove(createOutput.FilePath) + + // Apply Floyd-Steinberg dithering across full width to create gradient + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "draw_with_dither", + Arguments: map[string]any{ + "sprite_path": createOutput.FilePath, + "layer_name": "Layer 1", + "frame_number": 1, + "region": map[string]any{ + "x": 0, + "y": 0, + "width": 64, + "height": 64, + }, + "color1": "#001F3FFF", // Dark blue + "color2": "#7FDBFFFF", // Light blue + "pattern": "floyd_steinberg", + "density": 0.5, + }, + }) + require.NoError(t, err) + require.False(t, result.IsError) + + // Read back pixels to verify gradient was created + readResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: "get_pixels", + Arguments: map[string]any{ + "sprite_path": createOutput.FilePath, + "layer_name": "Layer 1", + "frame_number": 1, + "x": 0, + "y": 0, + "width": 64, + "height": 64, + }, + }) + require.NoError(t, err) + require.False(t, readResult.IsError) + + var readOutput struct { + Pixels []struct { + X int `json:"x"` + Y int `json:"y"` + Color string `json:"color"` + } `json:"pixels"` + } + json.Unmarshal([]byte(readResult.Content[0].(*mcp.TextContent).Text), &readOutput) + + // Verify we got pixels back + require.NotEmpty(t, readOutput.Pixels, "Should have pixels") + + // Count occurrences of each color + color1Count := 0 + color2Count := 0 + color1 := "#001F3FFF" + color2 := "#7FDBFFFF" + + for _, pixel := range readOutput.Pixels { + if pixel.Color == color1 { + color1Count++ + } else if pixel.Color == color2 { + color2Count++ + } + } + + // Verify we have a mix of both colors (not solid blue) + assert.Greater(t, color1Count, 0, "Should have some dark blue pixels") + assert.Greater(t, color2Count, 0, "Should have some light blue pixels") + + // Verify ratio is reasonable (gradient should use both colors significantly) + totalPixels := len(readOutput.Pixels) + color1Ratio := float64(color1Count) / float64(totalPixels) + color2Ratio := float64(color2Count) / float64(totalPixels) + + // Each color should be at least 20% of pixels (gradient uses both colors) + assert.Greater(t, color1Ratio, 0.2, "Dark blue should be at least 20%% of pixels") + assert.Greater(t, color2Ratio, 0.2, "Light blue should be at least 20%% of pixels") + + // Verify pixels vary across the width (error diffusion creates variation) + // Check first column vs last column - should have different color distributions + firstColColor1 := 0 + firstColColor2 := 0 + lastColColor1 := 0 + lastColColor2 := 0 + + for _, pixel := range readOutput.Pixels { + if pixel.X == 0 { + if pixel.Color == color1 { + firstColColor1++ + } else if pixel.Color == color2 { + firstColColor2++ + } + } + if pixel.X == 63 { + if pixel.Color == color1 { + lastColColor1++ + } else if pixel.Color == color2 { + lastColColor2++ + } + } + } + + // First and last columns should have different dominant colors (gradient works) + firstColDominant := firstColColor1 > firstColColor2 + lastColDominant := lastColColor1 > lastColColor2 + + assert.NotEqual(t, firstColDominant, lastColDominant, + "First and last columns should have different dominant colors (gradient effect)") +} + // Unit tests for isValidHexColor helper function func TestIsValidHexColor(t *testing.T) { tests := []struct { From d458dc35b13b3363e7765a9eb0b3167edbeed3a5 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 17 Oct 2025 22:55:58 -0700 Subject: [PATCH 2/2] fix: address Copilot review feedback for Floyd-Steinberg - Update function documentation to accurately describe gradient generation and clarify that density parameter is unused - Add edge case handling for width=1 to prevent division by zero - Verified all tests pass including new width=1 edge case --- pkg/aseprite/lua_drawing.go | 39 ++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/pkg/aseprite/lua_drawing.go b/pkg/aseprite/lua_drawing.go index 9dbbf72..12a608b 100644 --- a/pkg/aseprite/lua_drawing.go +++ b/pkg/aseprite/lua_drawing.go @@ -823,8 +823,9 @@ print("Dithering applied successfully")`, // // where X is the current pixel being processed. // -// Parameters match DrawWithDither but the density parameter is interpreted as a threshold: -// pixels with luminance < density*255 map to color2, otherwise color1. +// The implementation creates a horizontal gradient from color1 to color2 and applies error +// diffusion to produce smooth transitions. The density parameter is currently unused as the +// gradient quality is controlled by the error diffusion algorithm itself. func generateFloydSteinbergLua(layerName string, frameNumber int, x, y, width, height int, c1, c2 Color, density float64) string { return fmt.Sprintf(`local spr = app.activeSprite if not spr then @@ -928,7 +929,12 @@ app.transaction(function() for px = 0, %d - 1 do -- Calculate gradient position (0.0 to 1.0 across width) - local gradient_pos = px / (%d - 1) + local gradient_pos + if %d == 1 then + gradient_pos = 0 + else + gradient_pos = px / (%d - 1) + end -- Calculate ideal gradient color at this position local ideal_r = color1_r + (color2_r - color1_r) * gradient_pos @@ -1033,17 +1039,18 @@ print("Dithering applied successfully")`, frameNumber, frameNumber, c1.R, c1.G, c1.B, c1.A, c2.R, c2.G, c2.B, c2.A, - width, // line 914: error buffer width - height, // line 919: py loop - width, // line 924: clear buffer width - width, // line 929: px loop - width, // line 931: gradient calculation - width, // line 966: right neighbor check - height, // line 971: bottom neighbor check - width, // line 980: bottom-right check - width, // line 1006: right neighbor check (RGB) - height, // line 1011: bottom neighbor check (RGB) - width, // line 1020: bottom-right check (RGB) - x, // line 1028: x coordinate - y) // line 1028: y coordinate + width, // line 915: error buffer width + height, // line 920: py loop + width, // line 925: clear buffer width + width, // line 930: px loop + width, // line 933: width check for division by zero + width, // line 936: gradient calculation + width, // line 967: right neighbor check + height, // line 972: bottom neighbor check + width, // line 981: bottom-right check + width, // line 1007: right neighbor check (RGB) + height, // line 1012: bottom neighbor check (RGB) + width, // line 1021: bottom-right check (RGB) + x, // line 1031: x coordinate + y) // line 1031: y coordinate }