QOIWriter.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /*
  2. * Copyright (c) 2022, Olivier De Cannière <olivier.decanniere96@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "QOIWriter.h"
  7. #include <AK/DeprecatedString.h>
  8. #include <AK/Endian.h>
  9. namespace Gfx {
  10. static constexpr Array<u8, 4> qoi_magic_bytes = { 'q', 'o', 'i', 'f' };
  11. static constexpr Array<u8, 8> qoi_end_marker = { 0, 0, 0, 0, 0, 0, 0, 1 };
  12. enum class Colorspace {
  13. sRGB,
  14. Linear,
  15. };
  16. enum class Channels {
  17. RGB,
  18. RGBA,
  19. };
  20. ByteBuffer QOIWriter::encode(Bitmap const& bitmap)
  21. {
  22. QOIWriter writer;
  23. writer.add_header(bitmap.width(), bitmap.height(), Channels::RGBA, Colorspace::sRGB);
  24. Color previous_pixel = { 0, 0, 0, 255 };
  25. bool creating_run = false;
  26. int run_length = 0;
  27. for (auto y = 0; y < bitmap.height(); y++) {
  28. for (auto x = 0; x < bitmap.width(); x++) {
  29. auto pixel = bitmap.get_pixel(x, y);
  30. // Check for at most 62 consecutive identical pixels.
  31. if (pixel == previous_pixel) {
  32. if (!creating_run) {
  33. creating_run = true;
  34. run_length = 0;
  35. writer.insert_into_running_array(pixel);
  36. }
  37. run_length++;
  38. // If the run reaches a maximum length of 62 or if this is the last pixel then create the chunk.
  39. if (run_length == 62 || (y == bitmap.height() - 1 && x == bitmap.width() - 1)) {
  40. writer.add_run_chunk(run_length);
  41. creating_run = false;
  42. }
  43. continue;
  44. }
  45. // Run ended with the previous pixel. Create a chunk for it and continue processing this pixel.
  46. if (creating_run) {
  47. writer.add_run_chunk(run_length);
  48. creating_run = false;
  49. }
  50. // Check if the pixel matches a pixel in the running array.
  51. auto index = pixel_hash_function(pixel);
  52. auto& array_pixel = writer.running_array[index];
  53. if (array_pixel == pixel) {
  54. writer.add_index_chunk(index);
  55. previous_pixel = pixel;
  56. continue;
  57. }
  58. writer.running_array[index] = pixel;
  59. // Check if pixel can be expressed as a difference of the previous pixel.
  60. if (pixel.alpha() == previous_pixel.alpha()) {
  61. int red_difference = pixel.red() - previous_pixel.red();
  62. int green_difference = pixel.green() - previous_pixel.green();
  63. int blue_difference = pixel.blue() - previous_pixel.blue();
  64. int relative_red_difference = red_difference - green_difference;
  65. int relative_blue_difference = blue_difference - green_difference;
  66. if (red_difference > -3 && red_difference < 2
  67. && green_difference > -3 && green_difference < 2
  68. && blue_difference > -3 && blue_difference < 2) {
  69. writer.add_diff_chunk(red_difference, green_difference, blue_difference);
  70. previous_pixel = pixel;
  71. continue;
  72. }
  73. if (relative_red_difference > -9 && relative_red_difference < 8
  74. && green_difference > -33 && green_difference < 32
  75. && relative_blue_difference > -9 && relative_blue_difference < 8) {
  76. writer.add_luma_chunk(relative_red_difference, green_difference, relative_blue_difference);
  77. previous_pixel = pixel;
  78. continue;
  79. }
  80. writer.add_rgb_chunk(pixel.red(), pixel.green(), pixel.blue());
  81. previous_pixel = pixel;
  82. continue;
  83. }
  84. previous_pixel = pixel;
  85. // Write full color values.
  86. writer.add_rgba_chunk(pixel.red(), pixel.green(), pixel.blue(), pixel.alpha());
  87. }
  88. }
  89. writer.add_end_marker();
  90. return ByteBuffer::copy(writer.m_data).release_value_but_fixme_should_propagate_errors();
  91. }
  92. void QOIWriter::add_header(u32 width, u32 height, Channels channels = Channels::RGBA, Colorspace color_space = Colorspace::sRGB)
  93. {
  94. // FIXME: Handle RGB and all linear channels.
  95. if (channels == Channels::RGB || color_space == Colorspace::Linear)
  96. TODO();
  97. m_data.append(qoi_magic_bytes.data(), sizeof(qoi_magic_bytes));
  98. auto big_endian_width = AK::convert_between_host_and_big_endian(width);
  99. m_data.append(bit_cast<u8*>(&big_endian_width), sizeof(width));
  100. auto big_endian_height = AK::convert_between_host_and_big_endian(height);
  101. m_data.append(bit_cast<u8*>(&big_endian_height), sizeof(height));
  102. // Number of channels: 3 = RGB, 4 = RGBA.
  103. m_data.append(4);
  104. // Colorspace: 0 = sRGB, 1 = all linear channels.
  105. m_data.append(color_space == Colorspace::sRGB ? 0 : 1);
  106. }
  107. void QOIWriter::add_rgb_chunk(u8 r, u8 g, u8 b)
  108. {
  109. constexpr static u8 rgb_tag = 0b1111'1110;
  110. m_data.append(rgb_tag);
  111. m_data.append(r);
  112. m_data.append(g);
  113. m_data.append(b);
  114. }
  115. void QOIWriter::add_rgba_chunk(u8 r, u8 g, u8 b, u8 a)
  116. {
  117. constexpr static u8 rgba_tag = 0b1111'1111;
  118. m_data.append(rgba_tag);
  119. m_data.append(r);
  120. m_data.append(g);
  121. m_data.append(b);
  122. m_data.append(a);
  123. }
  124. void QOIWriter::add_index_chunk(unsigned int index)
  125. {
  126. constexpr static u8 index_tag = 0b0000'0000;
  127. u8 chunk = index_tag | index;
  128. m_data.append(chunk);
  129. }
  130. void QOIWriter::add_diff_chunk(i8 red_difference, i8 green_difference, i8 blue_difference)
  131. {
  132. constexpr static u8 diff_tag = 0b0100'0000;
  133. u8 bias = 2;
  134. u8 red = red_difference + bias;
  135. u8 green = green_difference + bias;
  136. u8 blue = blue_difference + bias;
  137. u8 chunk = diff_tag | (red << 4) | (green << 2) | blue;
  138. m_data.append(chunk);
  139. }
  140. void QOIWriter::add_luma_chunk(i8 relative_red_difference, i8 green_difference, i8 relative_blue_difference)
  141. {
  142. constexpr static u8 luma_tag = 0b1000'0000;
  143. u8 green_bias = 32;
  144. u8 red_blue_bias = 8;
  145. u8 chunk1 = luma_tag | (green_difference + green_bias);
  146. u8 chunk2 = ((relative_red_difference + red_blue_bias) << 4) | (relative_blue_difference + red_blue_bias);
  147. m_data.append(chunk1);
  148. m_data.append(chunk2);
  149. }
  150. void QOIWriter::add_run_chunk(unsigned run_length)
  151. {
  152. constexpr static u8 run_tag = 0b1100'0000;
  153. int bias = -1;
  154. u8 chunk = run_tag | (run_length + bias);
  155. m_data.append(chunk);
  156. }
  157. void QOIWriter::add_end_marker()
  158. {
  159. m_data.append(qoi_end_marker.data(), sizeof(qoi_end_marker));
  160. }
  161. u32 QOIWriter::pixel_hash_function(Color pixel)
  162. {
  163. return (pixel.red() * 3 + pixel.green() * 5 + pixel.blue() * 7 + pixel.alpha() * 11) % 64;
  164. }
  165. void QOIWriter::insert_into_running_array(Color pixel)
  166. {
  167. auto index = pixel_hash_function(pixel);
  168. running_array[index] = pixel;
  169. }
  170. }