JPEGWriter.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. /*
  2. * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "JPEGWriter.h"
  7. #include "JPEGShared.h"
  8. #include "JPEGWriterTables.h"
  9. #include <AK/BitStream.h>
  10. #include <AK/Endian.h>
  11. #include <AK/Function.h>
  12. #include <LibGfx/Bitmap.h>
  13. namespace Gfx {
  14. namespace {
  15. // This is basically a BigEndianOutputBitStream, the only difference
  16. // is that it appends 0x00 after each 0xFF when it writes bits.
  17. class JPEGBigEndianOutputBitStream : public Stream {
  18. public:
  19. explicit JPEGBigEndianOutputBitStream(Stream& stream)
  20. : m_stream(stream)
  21. {
  22. }
  23. virtual ErrorOr<Bytes> read_some(Bytes) override
  24. {
  25. return Error::from_errno(EBADF);
  26. }
  27. virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
  28. {
  29. VERIFY(m_bit_offset == 0);
  30. return m_stream.write_some(bytes);
  31. }
  32. template<Unsigned T>
  33. ErrorOr<void> write_bits(T value, size_t bit_count)
  34. {
  35. VERIFY(m_bit_offset <= 7);
  36. while (bit_count > 0) {
  37. u8 const next_bit = (value >> (bit_count - 1)) & 1;
  38. bit_count--;
  39. m_current_byte <<= 1;
  40. m_current_byte |= next_bit;
  41. m_bit_offset++;
  42. if (m_bit_offset > 7) {
  43. TRY(m_stream.write_value(m_current_byte));
  44. if (m_current_byte == 0xFF)
  45. TRY(m_stream.write_value<u8>(0));
  46. m_bit_offset = 0;
  47. m_current_byte = 0;
  48. }
  49. }
  50. return {};
  51. }
  52. virtual bool is_eof() const override
  53. {
  54. return true;
  55. }
  56. virtual bool is_open() const override
  57. {
  58. return m_stream.is_open();
  59. }
  60. virtual void close() override
  61. {
  62. }
  63. ErrorOr<void> align_to_byte_boundary(u8 filler = 0x0)
  64. {
  65. if (m_bit_offset == 0)
  66. return {};
  67. TRY(write_bits(filler, 8 - m_bit_offset));
  68. VERIFY(m_bit_offset == 0);
  69. return {};
  70. }
  71. private:
  72. Stream& m_stream;
  73. u8 m_current_byte { 0 };
  74. size_t m_bit_offset { 0 };
  75. };
  76. class JPEGEncodingContext {
  77. public:
  78. JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream)
  79. : m_bit_stream(move(output_stream))
  80. {
  81. }
  82. ErrorOr<void> initialize_mcu(Bitmap const& bitmap)
  83. {
  84. u64 const horizontal_macroblocks = bitmap.width() / 8 + (bitmap.width() % 8 == 0 ? 0 : 1);
  85. m_vertical_macroblocks = bitmap.height() / 8 + (bitmap.height() % 8 == 0 ? 0 : 1);
  86. TRY(m_macroblocks.try_resize(horizontal_macroblocks * m_vertical_macroblocks));
  87. for (u16 y {}; y < bitmap.height(); ++y) {
  88. u16 const vertical_macroblock_index = y / 8;
  89. u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
  90. for (u16 x {}; x < bitmap.width(); ++x) {
  91. u16 const horizontal_macroblock_index = x / 8;
  92. u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8;
  93. auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index];
  94. auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset;
  95. auto const original_pixel = bitmap.get_pixel(x, y);
  96. // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension:
  97. // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items
  98. // 7 - Conversion to and from RGB
  99. auto const y_ = clamp(0.299 * original_pixel.red() + 0.587 * original_pixel.green() + 0.114 * original_pixel.blue(), 0, 255);
  100. auto const cb = clamp(-0.1687 * original_pixel.red() - 0.3313 * original_pixel.green() + 0.5 * original_pixel.blue() + 128, 0, 255);
  101. auto const cr = clamp(0.5 * original_pixel.red() - 0.4187 * original_pixel.green() - 0.0813 * original_pixel.blue() + 128, 0, 255);
  102. // A.3.1 - Level shift
  103. macroblock.r[pixel_offset] = y_ - 128;
  104. macroblock.g[pixel_offset] = cb - 128;
  105. macroblock.b[pixel_offset] = cr - 128;
  106. }
  107. }
  108. return {};
  109. }
  110. static Array<double, 64> create_cosine_lookup_table()
  111. {
  112. static constexpr double pi_over_16 = AK::Pi<double> / 16;
  113. Array<double, 64> table;
  114. for (u8 u = 0; u < 8; ++u) {
  115. for (u8 x = 0; x < 8; ++x)
  116. table[u * 8 + x] = cos((2 * x + 1) * u * pi_over_16);
  117. }
  118. return table;
  119. }
  120. void fdct_and_quantization()
  121. {
  122. static auto cosine_table = create_cosine_lookup_table();
  123. for (auto& macroblock : m_macroblocks) {
  124. constexpr double inverse_sqrt_2 = M_SQRT1_2;
  125. auto const convert_one_component = [&](i16 component[], QuantizationTable const& table) {
  126. Array<i16, 64> result {};
  127. auto const sum_xy = [&](u8 u, u8 v) {
  128. double sum {};
  129. for (u8 x {}; x < 8; ++x) {
  130. for (u8 y {}; y < 8; ++y)
  131. sum += component[x * 8 + y] * cosine_table[u * 8 + x] * cosine_table[v * 8 + y];
  132. }
  133. return sum;
  134. };
  135. for (u8 u {}; u < 7; ++u) {
  136. double const cu = u == 0 ? inverse_sqrt_2 : 1;
  137. for (u8 v {}; v < 7; ++v) {
  138. auto const table_index = u * 8 + v;
  139. double const cv = v == 0 ? inverse_sqrt_2 : 1;
  140. // A.3.3 - FDCT and IDCT
  141. double const fdct = cu * cv * sum_xy(u, v) / 4;
  142. // A.3.4 - DCT coefficient quantization
  143. i16 const quantized = round(fdct / table.table[table_index]);
  144. result[table_index] = quantized;
  145. }
  146. }
  147. for (u8 i {}; i < result.size(); ++i)
  148. component[i] = result[i];
  149. };
  150. convert_one_component(macroblock.y, m_luminance_quantization_table);
  151. convert_one_component(macroblock.cb, m_chrominance_quantization_table);
  152. convert_one_component(macroblock.cr, m_chrominance_quantization_table);
  153. }
  154. }
  155. ErrorOr<void> write_huffman_stream()
  156. {
  157. for (auto& macroblock : m_macroblocks) {
  158. TRY(encode_dc(dc_luminance_huffman_table, macroblock.y, 0));
  159. TRY(encode_ac(ac_luminance_huffman_table, macroblock.y));
  160. TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cb, 1));
  161. TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cb));
  162. TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cr, 2));
  163. TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cr));
  164. }
  165. TRY(m_bit_stream.align_to_byte_boundary(0xFF));
  166. return {};
  167. }
  168. void set_luminance_quantization_table(QuantizationTable const& table, int quality)
  169. {
  170. set_quantization_table(m_luminance_quantization_table, table, quality);
  171. }
  172. void set_chrominance_quantization_table(QuantizationTable const& table, int quality)
  173. {
  174. set_quantization_table(m_chrominance_quantization_table, table, quality);
  175. }
  176. QuantizationTable const& luminance_quantization_table() const
  177. {
  178. return m_luminance_quantization_table;
  179. }
  180. QuantizationTable const& chrominance_quantization_table() const
  181. {
  182. return m_chrominance_quantization_table;
  183. }
  184. OutputHuffmanTable dc_luminance_huffman_table;
  185. OutputHuffmanTable dc_chrominance_huffman_table;
  186. OutputHuffmanTable ac_luminance_huffman_table;
  187. OutputHuffmanTable ac_chrominance_huffman_table;
  188. private:
  189. static void set_quantization_table(QuantizationTable& destination, QuantizationTable const& source, int quality)
  190. {
  191. // In order to be compatible with libjpeg-turbo, we use the same coefficients as them.
  192. quality = clamp(quality, 1, 100);
  193. if (quality < 50)
  194. quality = 5000 / quality;
  195. else
  196. quality = 200 - quality * 2;
  197. destination = source;
  198. for (u8 i {}; i < 64; ++i) {
  199. auto const shifted_value = (destination.table[i] * quality + 50) / 100;
  200. destination.table[i] = clamp(shifted_value, 1, 255);
  201. }
  202. }
  203. ErrorOr<void> write_symbol(OutputHuffmanTable::Symbol symbol)
  204. {
  205. return m_bit_stream.write_bits(symbol.word, symbol.code_length);
  206. };
  207. ErrorOr<void> encode_dc(OutputHuffmanTable const& dc_table, i16 const component[], u8 component_id)
  208. {
  209. // F.1.2.1.3 - Huffman encoding procedures for DC coefficients
  210. auto diff = component[0] - m_last_dc_values[component_id];
  211. m_last_dc_values[component_id] = component[0];
  212. auto const size = csize(diff);
  213. TRY(write_symbol(dc_table.from_input_byte(size)));
  214. if (diff < 0)
  215. diff -= 1;
  216. TRY(m_bit_stream.write_bits<u16>(diff, size));
  217. return {};
  218. }
  219. ErrorOr<void> encode_ac(OutputHuffmanTable const& ac_table, i16 const component[])
  220. {
  221. {
  222. // F.2 - Procedure for sequential encoding of AC coefficients with Huffman coding
  223. u32 k {};
  224. u32 r {};
  225. while (k < 63) {
  226. k++;
  227. auto coefficient = component[zigzag_map[k]];
  228. if (coefficient == 0) {
  229. if (k == 63) {
  230. TRY(write_symbol(ac_table.from_input_byte(0x00)));
  231. break;
  232. }
  233. r += 1;
  234. continue;
  235. }
  236. while (r > 15) {
  237. TRY(write_symbol(ac_table.from_input_byte(0xF0)));
  238. r -= 16;
  239. }
  240. {
  241. // F.3 - Sequential encoding of a non-zero AC coefficient
  242. auto const ssss = csize(coefficient);
  243. auto const rs = (r << 4) + ssss;
  244. TRY(write_symbol(ac_table.from_input_byte(rs)));
  245. if (coefficient < 0)
  246. coefficient -= 1;
  247. TRY(m_bit_stream.write_bits<u16>(coefficient, ssss));
  248. }
  249. r = 0;
  250. }
  251. }
  252. return {};
  253. }
  254. static u8 csize(i16 coefficient)
  255. {
  256. VERIFY(coefficient >= -2047 && coefficient <= 2047);
  257. return floor(log2(abs(coefficient))) + 1;
  258. };
  259. QuantizationTable m_luminance_quantization_table {};
  260. QuantizationTable m_chrominance_quantization_table {};
  261. Vector<Macroblock> m_macroblocks {};
  262. Array<i16, 3> m_last_dc_values {};
  263. u64 m_vertical_macroblocks {};
  264. JPEGBigEndianOutputBitStream m_bit_stream;
  265. };
  266. ErrorOr<void> add_start_of_image(Stream& stream)
  267. {
  268. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI));
  269. return {};
  270. }
  271. ErrorOr<void> add_end_of_image(Stream& stream)
  272. {
  273. TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI));
  274. return {};
  275. }
  276. ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, Bitmap const& bitmap)
  277. {
  278. // B.2.2 - Frame header syntax
  279. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0));
  280. // Lf = 8 + 3 × Nf, we only support a single image per frame so Nf = 3
  281. TRY(stream.write_value<BigEndian<u16>>(17));
  282. // P
  283. TRY(stream.write_value<u8>(8));
  284. // Y
  285. TRY(stream.write_value<BigEndian<u16>>(bitmap.height()));
  286. // X
  287. TRY(stream.write_value<BigEndian<u16>>(bitmap.width()));
  288. // Nf, as mentioned earlier, we only support Nf = 3
  289. TRY(stream.write_value<u8>(3));
  290. // Encode 3 components
  291. for (u8 i {}; i < 3; ++i) {
  292. // Ci
  293. TRY(stream.write_value<u8>(i + 1));
  294. // Hi and Vi
  295. TRY(stream.write_value<u8>((1 << 4) | 1));
  296. // Tqi
  297. TRY(stream.write_value<u8>((i == 0 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id));
  298. }
  299. return {};
  300. }
  301. ErrorOr<void> add_quantization_table(Stream& stream, QuantizationTable const& table)
  302. {
  303. // B.2.4.1 - Quantization table-specification syntax
  304. TRY(stream.write_value<BigEndian<Marker>>(JPEG_DQT));
  305. // Lq = 2 + 1 * 65
  306. TRY(stream.write_value<BigEndian<u16>>(2 + 65));
  307. // Pq and Tq
  308. TRY(stream.write_value<u8>((0 << 4) | table.id));
  309. for (auto coefficient : table.table)
  310. TRY(stream.write_value<u8>(coefficient));
  311. return {};
  312. }
  313. ErrorOr<Vector<Vector<u8>, 16>> sort_symbols_per_size(OutputHuffmanTable const& table)
  314. {
  315. // JPEG only allows symbol with a size less than or equal to 16.
  316. Vector<Vector<u8>, 16> output {};
  317. TRY(output.try_resize(16));
  318. for (auto const& symbol : table.table)
  319. TRY(output[symbol.code_length - 1].try_append(symbol.input_byte));
  320. return output;
  321. }
  322. ErrorOr<void> add_huffman_table(Stream& stream, OutputHuffmanTable const& table)
  323. {
  324. // B.2.4.2 - Huffman table-specification syntax
  325. TRY(stream.write_value<BigEndian<Marker>>(JPEG_DHT));
  326. // Lh
  327. TRY(stream.write_value<BigEndian<u16>>(2 + 17 + table.table.size()));
  328. // Tc and Th
  329. TRY(stream.write_value<u8>(table.id));
  330. auto const vectorized_table = TRY(sort_symbols_per_size(table));
  331. for (auto const& symbol_vector : vectorized_table)
  332. TRY(stream.write_value<u8>(symbol_vector.size()));
  333. for (auto const& symbol_vector : vectorized_table) {
  334. for (auto symbol : symbol_vector)
  335. TRY(stream.write_value<u8>(symbol));
  336. }
  337. return {};
  338. }
  339. ErrorOr<void> add_scan_header(Stream& stream)
  340. {
  341. // B.2.3 - Scan header syntax
  342. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS));
  343. // Ls - 6 + 2 × Ns
  344. TRY(stream.write_value<BigEndian<u16>>(6 + 2 * 3));
  345. // Ns
  346. TRY(stream.write_value<u8>(3));
  347. // Encode 3 components
  348. for (u8 i {}; i < 3; ++i) {
  349. // Csj
  350. TRY(stream.write_value<u8>(i + 1));
  351. // Tdj and Taj
  352. // We're using 0 for luminance and 1 for chrominance
  353. u8 const huffman_identifier = i > 0 ? 1 : 0;
  354. TRY(stream.write_value<u8>((huffman_identifier << 4) | huffman_identifier));
  355. }
  356. // Ss
  357. TRY(stream.write_value<u8>(0));
  358. // Se
  359. TRY(stream.write_value<u8>(63));
  360. // Ah and Al
  361. TRY(stream.write_value<u8>((0 << 4) | 0));
  362. return {};
  363. }
  364. }
  365. ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap)
  366. {
  367. JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
  368. // FIXME: Let's take the quality as an option instead of hardcoding it
  369. // (there might also be a bug with quantization tables :^)).
  370. context.set_luminance_quantization_table(s_default_luminance_quantization_table, 100);
  371. context.set_chrominance_quantization_table(s_default_chrominance_quantization_table, 100);
  372. context.dc_luminance_huffman_table = s_default_dc_luminance_huffman_table;
  373. context.dc_chrominance_huffman_table = s_default_dc_chrominance_huffman_table;
  374. context.ac_luminance_huffman_table = s_default_ac_luminance_huffman_table;
  375. context.ac_chrominance_huffman_table = s_default_ac_chrominance_huffman_table;
  376. TRY(add_start_of_image(stream));
  377. TRY(add_frame_header(stream, context, bitmap));
  378. TRY(add_quantization_table(stream, context.luminance_quantization_table()));
  379. TRY(add_quantization_table(stream, context.chrominance_quantization_table()));
  380. TRY(add_huffman_table(stream, context.dc_luminance_huffman_table));
  381. TRY(add_huffman_table(stream, context.dc_chrominance_huffman_table));
  382. TRY(add_huffman_table(stream, context.ac_luminance_huffman_table));
  383. TRY(add_huffman_table(stream, context.ac_chrominance_huffman_table));
  384. TRY(add_scan_header(stream));
  385. TRY(context.initialize_mcu(bitmap));
  386. context.fdct_and_quantization();
  387. TRY(context.write_huffman_stream());
  388. TRY(add_end_of_image(stream));
  389. return {};
  390. }
  391. }