From 560b0f07d1cf14e280089e065a8cd9d85d797ada Mon Sep 17 00:00:00 2001 From: John Thomas McDole Date: Sat, 14 Dec 2024 13:22:18 -0800 Subject: [PATCH 1/2] Unmultiply colors before brightening --- packages/flame/lib/src/extensions/image.dart | 42 +++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/flame/lib/src/extensions/image.dart b/packages/flame/lib/src/extensions/image.dart index 6dc0ce3a9b..b61b4a1935 100644 --- a/packages/flame/lib/src/extensions/image.dart +++ b/packages/flame/lib/src/extensions/image.dart @@ -78,17 +78,37 @@ extension ImageExtension on Image { final newPixelData = Uint8List(pixelData.length); for (var i = 0; i < pixelData.length; i += 4) { - final color = Color.fromARGB( - pixelData[i + 3], - pixelData[i + 0], - pixelData[i + 1], - pixelData[i + 2], - ).brighten(amount); - - newPixelData[i] = (color.r * 255).round(); - newPixelData[i + 1] = (color.g * 255).round(); - newPixelData[i + 2] = (color.b * 255).round(); - newPixelData[i + 3] = (color.a * 255).round(); + final a = pixelData[i + 3] / 255; + + // Lets avoid division by zero. + if (a == 0) { + newPixelData[i + 0] = pixelData[i + 0]; + newPixelData[i + 1] = pixelData[i + 1]; + newPixelData[i + 2] = pixelData[i + 2]; + newPixelData[i + 3] = pixelData[i + 3]; + continue; + } + + // Unmultiply the color + var r = (pixelData[i + 0] / 255) / a; + var g = (pixelData[i + 1] / 255) / a; + var b = (pixelData[i + 2] / 255) / a; + + // Brighten in a color accurate way + r = r + (1.0 - r) * amount; + g = g + (1.0 - g) * amount; + b = b + (1.0 - b) * amount; + + // Clamp + r = r.clamp(0, 1.0); + g = g.clamp(0, 1.0); + b = b.clamp(0, 1.0); + + // Pre-multiply the new color. + newPixelData[i + 0] = (r * a * 255).round(); + newPixelData[i + 1] = (g * a * 255).round(); + newPixelData[i + 2] = (b * a * 255).round(); + newPixelData[i + 3] = pixelData[i + 3]; } return fromPixels(newPixelData, width, height); } From fba062607c6abf7e7a67ae47f9316a9e7bcb4989 Mon Sep 17 00:00:00 2001 From: John Thomas McDole Date: Sat, 14 Dec 2024 20:57:35 -0800 Subject: [PATCH 2/2] Update darken and switch to HSL. --- packages/flame/lib/src/extensions/image.dart | 67 ++++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/packages/flame/lib/src/extensions/image.dart b/packages/flame/lib/src/extensions/image.dart index b61b4a1935..160f90507b 100644 --- a/packages/flame/lib/src/extensions/image.dart +++ b/packages/flame/lib/src/extensions/image.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:flame/extensions.dart'; import 'package:flame/palette.dart'; +import 'package:flutter/painting.dart'; export 'dart:ui' show Image; @@ -53,17 +54,39 @@ extension ImageExtension on Image { final newPixelData = Uint8List(pixelData.length); for (var i = 0; i < pixelData.length; i += 4) { - final color = Color.fromARGB( - pixelData[i + 3], - pixelData[i + 0], - pixelData[i + 1], - pixelData[i + 2], - ).darken(amount); - - newPixelData[i] = (color.r * 255).round(); - newPixelData[i + 1] = (color.g * 255).round(); - newPixelData[i + 2] = (color.b * 255).round(); - newPixelData[i + 3] = (color.a * 255).round(); + final a = pixelData[i + 3] / 255; + + // Lets avoid division by zero. + if (a == 0) { + newPixelData[i + 0] = pixelData[i + 0]; + newPixelData[i + 1] = pixelData[i + 1]; + newPixelData[i + 2] = pixelData[i + 2]; + newPixelData[i + 3] = pixelData[i + 3]; + continue; + } + + // Reverse premultiplied alpha. + var r = (pixelData[i + 0] / 255) / a; + var g = (pixelData[i + 1] / 255) / a; + var b = (pixelData[i + 2] / 255) / a; + + // Convert to HSL (Hue, Saturation, and Lightness). + var hsl = + HSLColor.fromColor(Color.from(alpha: a, red: r, green: g, blue: b)); + hsl = hsl.withLightness( + clampDouble(hsl.lightness * amount, 0, 1.0), + ); + + final color = hsl.toColor(); + r = color.r; + g = color.g; + b = color.b; + + // Premultiply the new color. + newPixelData[i + 0] = (r * a * 255).round(); + newPixelData[i + 1] = (g * a * 255).round(); + newPixelData[i + 2] = (b * a * 255).round(); + newPixelData[i + 3] = pixelData[i + 3]; } return fromPixels(newPixelData, width, height); } @@ -89,22 +112,24 @@ extension ImageExtension on Image { continue; } - // Unmultiply the color + // Reverse premultiplied alpha. var r = (pixelData[i + 0] / 255) / a; var g = (pixelData[i + 1] / 255) / a; var b = (pixelData[i + 2] / 255) / a; - // Brighten in a color accurate way - r = r + (1.0 - r) * amount; - g = g + (1.0 - g) * amount; - b = b + (1.0 - b) * amount; + // Convert to HSL (Hue, Saturation, and Lightness). + var hsl = + HSLColor.fromColor(Color.from(alpha: a, red: r, green: g, blue: b)); + hsl = hsl.withLightness( + clampDouble(hsl.lightness + (1 - hsl.lightness) * amount, 0, 1.0), + ); - // Clamp - r = r.clamp(0, 1.0); - g = g.clamp(0, 1.0); - b = b.clamp(0, 1.0); + final color = hsl.toColor(); + r = color.r; + g = color.g; + b = color.b; - // Pre-multiply the new color. + // Premultiply the new color. newPixelData[i + 0] = (r * a * 255).round(); newPixelData[i + 1] = (g * a * 255).round(); newPixelData[i + 2] = (b * a * 255).round();