GIFWriter.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. * Copyright (c) 2024, Lucas Chollet <lucas.chollet@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/BitStream.h>
  7. #include <LibCompress/Lzw.h>
  8. #include <LibGfx/Bitmap.h>
  9. #include <LibGfx/ImageFormats/GIFWriter.h>
  10. #include <LibGfx/MedianCut.h>
  11. namespace Gfx {
  12. namespace {
  13. ErrorOr<void> write_header(Stream& stream)
  14. {
  15. // 17. Header
  16. TRY(stream.write_until_depleted("GIF89a"sv));
  17. return {};
  18. }
  19. ErrorOr<void> write_logical_descriptor(BigEndianOutputBitStream& stream, IntSize size)
  20. {
  21. // 18. Logical Screen Descriptor
  22. if (size.width() > NumericLimits<u16>::max() || size.height() > NumericLimits<u16>::max())
  23. return Error::from_string_literal("Bitmap size is too big for a GIF");
  24. TRY(stream.write_value<u16>(size.width()));
  25. TRY(stream.write_value<u16>(size.height()));
  26. // Global Color Table Flag
  27. TRY(stream.write_bits(false, 1));
  28. // Color Resolution
  29. TRY(stream.write_bits(6u, 3));
  30. // Sort Flag
  31. TRY(stream.write_bits(false, 1));
  32. // Size of Global Color Table
  33. TRY(stream.write_bits(0u, 3));
  34. // Background Color Index
  35. TRY(stream.write_value<u8>(0));
  36. // Pixel Aspect Ratio
  37. // NOTE: We can write a zero as most decoders discard the value.
  38. TRY(stream.write_value<u8>(0));
  39. return {};
  40. }
  41. ErrorOr<void> write_color_table(Stream& stream, ColorPalette const& palette)
  42. {
  43. // 19. Global Color Table or 21. Local Color Table.
  44. for (u16 i = 0; i < 256; ++i) {
  45. auto const color = i < palette.palette().size() ? palette.palette()[i] : Color::NamedColor::White;
  46. TRY(stream.write_value<u8>(color.red()));
  47. TRY(stream.write_value<u8>(color.green()));
  48. TRY(stream.write_value<u8>(color.blue()));
  49. }
  50. return {};
  51. }
  52. ErrorOr<void> write_image_data(Stream& stream, Bitmap const& bitmap, ColorPalette const& palette)
  53. {
  54. // 22. Table Based Image Data
  55. auto const pixel_number = static_cast<u32>(bitmap.width() * bitmap.height());
  56. auto indexes = TRY(ByteBuffer::create_uninitialized(pixel_number));
  57. for (u32 i = 0; i < pixel_number; ++i) {
  58. auto const color = Color::from_argb(*(bitmap.begin() + i));
  59. indexes[i] = palette.index_of_closest_color(color);
  60. }
  61. constexpr u8 lzw_minimum_code_size = 8;
  62. auto const encoded = TRY(Compress::LzwCompressor::compress_all(move(indexes), lzw_minimum_code_size));
  63. auto const number_of_subblocks = ceil_div(encoded.size(), 255ul);
  64. TRY(stream.write_value<u8>(lzw_minimum_code_size));
  65. for (u32 i = 0; i < number_of_subblocks; ++i) {
  66. auto const offset = i * 255;
  67. auto const to_write = min(255, encoded.size() - offset);
  68. TRY(stream.write_value<u8>(to_write));
  69. TRY(stream.write_until_depleted(encoded.bytes().slice(offset, to_write)));
  70. }
  71. // Block terminator
  72. TRY(stream.write_value<u8>(0));
  73. return {};
  74. }
  75. ErrorOr<void> write_image_descriptor(BigEndianOutputBitStream& stream, Bitmap const& bitmap, IntPoint at = {})
  76. {
  77. // 20. Image Descriptor
  78. // Image Separator
  79. TRY(stream.write_value<u8>(0x2c));
  80. // Image Left Position
  81. TRY(stream.write_value<u16>(at.x()));
  82. // Image Top Position
  83. TRY(stream.write_value<u16>(at.y()));
  84. // Image Width
  85. TRY(stream.write_value<u16>(bitmap.width()));
  86. // Image Height
  87. TRY(stream.write_value<u16>(bitmap.height()));
  88. // Local Color Table Flag
  89. TRY(stream.write_bits(true, 1));
  90. // Interlace Flag
  91. TRY(stream.write_bits(false, 1));
  92. // Sort Flag
  93. TRY(stream.write_bits(false, 1));
  94. // Reserved
  95. TRY(stream.write_bits(0u, 2));
  96. // Size of Local Color Table
  97. TRY(stream.write_bits(7u, 3));
  98. return {};
  99. }
  100. ErrorOr<void> write_graphic_control_extension(BigEndianOutputBitStream& stream, int duration_ms)
  101. {
  102. // 23. Graphic Control Extension
  103. // Extension Introducer
  104. TRY(stream.write_value<u8>(0x21));
  105. // Graphic Control Label
  106. TRY(stream.write_value<u8>(0xF9));
  107. // Block Size
  108. TRY(stream.write_value<u8>(4));
  109. // Packed Field
  110. // Reserved
  111. TRY(stream.write_bits(0u, 3));
  112. // Disposal Method
  113. TRY(stream.write_bits(0u, 3));
  114. // User Input Flag
  115. TRY(stream.write_bits(false, 1));
  116. // Transparency Flag
  117. TRY(stream.write_bits(false, 1));
  118. // Delay Time
  119. TRY(stream.write_value<u16>(duration_ms / 10));
  120. // Transparent Color Index
  121. TRY(stream.write_value<u8>(0));
  122. // Block Terminator
  123. TRY(stream.write_value<u8>(0));
  124. return {};
  125. }
  126. ErrorOr<void> write_trailer(Stream& stream)
  127. {
  128. TRY(stream.write_value<u8>(0x3B));
  129. return {};
  130. }
  131. class GIFAnimationWriter : public AnimationWriter {
  132. public:
  133. GIFAnimationWriter(SeekableStream& stream)
  134. : m_stream(stream)
  135. {
  136. }
  137. virtual ErrorOr<void> add_frame(Bitmap&, int, IntPoint) override;
  138. private:
  139. SeekableStream& m_stream;
  140. bool m_is_first_frame { true };
  141. };
  142. ErrorOr<void> GIFAnimationWriter::add_frame(Bitmap& bitmap, int duration_ms, IntPoint at = {})
  143. {
  144. // Let's get rid of the previously written trailer
  145. if (!m_is_first_frame)
  146. TRY(m_stream.seek(-1, SeekMode::FromCurrentPosition));
  147. m_is_first_frame = false;
  148. // Write a Table-Based Image
  149. BigEndianOutputBitStream bit_stream { MaybeOwned { m_stream } };
  150. TRY(write_graphic_control_extension(bit_stream, duration_ms));
  151. TRY(write_image_descriptor(bit_stream, bitmap, at));
  152. auto const palette = TRY(median_cut(bitmap, 256));
  153. TRY(write_color_table(m_stream, palette));
  154. TRY(write_image_data(m_stream, bitmap, palette));
  155. // We always write a trailer to ensure that the file is valid.
  156. TRY(write_trailer(m_stream));
  157. return {};
  158. }
  159. ErrorOr<void> write_netscape_extension(BigEndianOutputBitStream& stream, u16 loop_count)
  160. {
  161. // This is a vendor extension, its sole usage is to provide the loop count.
  162. // I used this link as a source: https://web.archive.org/web/19990418091037/http://www6.uniovi.es/gifanim/gifabout.htm
  163. // Extension Introducer
  164. TRY(stream.write_value<u8>(0x21));
  165. // Application Extension Label
  166. TRY(stream.write_value<u8>(0xFF));
  167. // Block Size
  168. constexpr auto netscape_signature = "NETSCAPE2.0"sv;
  169. TRY(stream.write_value<u8>(netscape_signature.length()));
  170. TRY(stream.write_until_depleted(netscape_signature));
  171. // Length of Data Sub-Block
  172. TRY(stream.write_value<u8>(3));
  173. // Undocumented
  174. TRY(stream.write_value<u8>(1));
  175. // Number of loops, 0 means infinite
  176. TRY(stream.write_value<u16>(loop_count));
  177. // Block Terminator
  178. TRY(stream.write_value<u8>(0));
  179. return {};
  180. }
  181. }
  182. ErrorOr<void> GIFWriter::encode(Stream& stream, Bitmap const& bitmap)
  183. {
  184. auto const palette = TRY(median_cut(bitmap, 256));
  185. TRY(write_header(stream));
  186. BigEndianOutputBitStream bit_stream { MaybeOwned<Stream> { stream } };
  187. TRY(write_logical_descriptor(bit_stream, bitmap.size()));
  188. // Write a Table-Based Image
  189. TRY(write_image_descriptor(bit_stream, bitmap));
  190. TRY(write_color_table(bit_stream, palette));
  191. TRY(write_image_data(stream, bitmap, palette));
  192. TRY(write_trailer(bit_stream));
  193. return {};
  194. }
  195. ErrorOr<NonnullOwnPtr<AnimationWriter>> GIFWriter::start_encoding_animation(SeekableStream& stream, IntSize dimensions, u16 loop_count)
  196. {
  197. TRY(write_header(stream));
  198. BigEndianOutputBitStream bit_stream { MaybeOwned<Stream> { stream } };
  199. TRY(write_logical_descriptor(bit_stream, dimensions));
  200. // Vendor extension to support looping
  201. TRY(write_netscape_extension(bit_stream, loop_count));
  202. return make<GIFAnimationWriter>(stream);
  203. }
  204. }