AnimationWriter.cpp 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /*
  2. * Copyright (c) 2024, Nico Weber <thakis@chromium.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibGfx/Bitmap.h>
  7. #include <LibGfx/ImageFormats/AnimationWriter.h>
  8. #include <LibGfx/Rect.h>
  9. namespace Gfx {
  10. AnimationWriter::~AnimationWriter() = default;
  11. static bool are_scanlines_equal(Bitmap const& a, Bitmap const& b, int y)
  12. {
  13. for (int x = 0; x < a.width(); ++x) {
  14. if (a.get_pixel(x, y) != b.get_pixel(x, y))
  15. return false;
  16. }
  17. return true;
  18. }
  19. static bool are_columns_equal(Bitmap const& a, Bitmap const& b, int x, int y1, int y2)
  20. {
  21. for (int y = y1; y < y2; ++y) {
  22. if (a.get_pixel(x, y) != b.get_pixel(x, y))
  23. return false;
  24. }
  25. return true;
  26. }
  27. static Gfx::IntRect rect_where_pixels_are_different(Bitmap const& a, Bitmap const& b)
  28. {
  29. VERIFY(a.size() == b.size());
  30. // FIXME: This works on physical pixels.
  31. VERIFY(a.scale() == 1);
  32. VERIFY(b.scale() == 1);
  33. int number_of_equal_pixels_at_top = 0;
  34. while (number_of_equal_pixels_at_top < a.height() && are_scanlines_equal(a, b, number_of_equal_pixels_at_top))
  35. ++number_of_equal_pixels_at_top;
  36. int number_of_equal_pixels_at_bottom = 0;
  37. while (number_of_equal_pixels_at_bottom < a.height() - number_of_equal_pixels_at_top && are_scanlines_equal(a, b, a.height() - number_of_equal_pixels_at_bottom - 1))
  38. ++number_of_equal_pixels_at_bottom;
  39. int const y1 = number_of_equal_pixels_at_top;
  40. int const y2 = a.height() - number_of_equal_pixels_at_bottom;
  41. int number_of_equal_pixels_at_left = 0;
  42. while (number_of_equal_pixels_at_left < a.width() && are_columns_equal(a, b, number_of_equal_pixels_at_left, y1, y2))
  43. ++number_of_equal_pixels_at_left;
  44. int number_of_equal_pixels_at_right = 0;
  45. while (number_of_equal_pixels_at_right < a.width() - number_of_equal_pixels_at_left && are_columns_equal(a, b, a.width() - number_of_equal_pixels_at_right - 1, y1, y2))
  46. ++number_of_equal_pixels_at_right;
  47. // WebP can only encode even-sized animation frame positions.
  48. // FIXME: Change API shape in some way so that the AnimationWriter base class doesn't have to know about this detail of a subclass.
  49. if (number_of_equal_pixels_at_left % 2 != 0)
  50. --number_of_equal_pixels_at_left;
  51. if (number_of_equal_pixels_at_top % 2 != 0)
  52. --number_of_equal_pixels_at_top;
  53. Gfx::IntRect rect;
  54. rect.set_x(number_of_equal_pixels_at_left);
  55. rect.set_y(number_of_equal_pixels_at_top);
  56. rect.set_width(a.width() - number_of_equal_pixels_at_left - number_of_equal_pixels_at_right);
  57. rect.set_height(a.height() - number_of_equal_pixels_at_top - number_of_equal_pixels_at_bottom);
  58. return rect;
  59. }
  60. ErrorOr<void> AnimationWriter::add_frame_relative_to_last_frame(Bitmap& frame, int duration_ms, RefPtr<Bitmap> last_frame)
  61. {
  62. if (!last_frame)
  63. return add_frame(frame, duration_ms);
  64. auto rect = rect_where_pixels_are_different(*last_frame, frame);
  65. // FIXME: It would be nice to have a way to crop a bitmap without copying the data.
  66. auto differences = TRY(frame.cropped(rect));
  67. // FIXME: Another idea: If all frames of the animation have no alpha,
  68. // this could set color values of pixels that are in the changed rect that are
  69. // equal to the last frame to transparent black and set the frame to be blended.
  70. // That might take less space after compression.
  71. // This assumes a replacement disposal method.
  72. return add_frame(differences, duration_ms, rect.location());
  73. }
  74. }