1// Clip Library
2// Copyright (c) 2018-2021 David Capello
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#include "clip.h"
8
9#include <algorithm>
10#include <vector>
11
12#include "png.h"
13
14namespace clip {
15namespace x11 {
16
17//////////////////////////////////////////////////////////////////////
18// Functions to convert clip::image into png data to store it in the
19// clipboard.
20
21void write_data_fn(png_structp png, png_bytep buf, png_size_t len) {
22 std::vector<uint8_t>& output = *(std::vector<uint8_t>*)png_get_io_ptr(png);
23 const size_t i = output.size();
24 output.resize(i+len);
25 std::copy(buf, buf+len, output.begin()+i);
26}
27
28bool write_png(const image& image,
29 std::vector<uint8_t>& output) {
30 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
31 nullptr, nullptr, nullptr);
32 if (!png)
33 return false;
34
35 png_infop info = png_create_info_struct(png);
36 if (!info) {
37 png_destroy_write_struct(&png, nullptr);
38 return false;
39 }
40
41 if (setjmp(png_jmpbuf(png))) {
42 png_destroy_write_struct(&png, &info);
43 return false;
44 }
45
46 png_set_write_fn(png,
47 (png_voidp)&output,
48 write_data_fn,
49 nullptr); // No need for a flush function
50
51 const image_spec& spec = image.spec();
52 int color_type = (spec.alpha_mask ?
53 PNG_COLOR_TYPE_RGB_ALPHA:
54 PNG_COLOR_TYPE_RGB);
55
56 png_set_IHDR(png, info,
57 spec.width, spec.height, 8, color_type,
58 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
59 png_write_info(png, info);
60 png_set_packing(png);
61
62 png_bytep row =
63 (png_bytep)png_malloc(png, png_get_rowbytes(png, info));
64
65 for (png_uint_32 y=0; y<spec.height; ++y) {
66 const uint32_t* src =
67 (const uint32_t*)(((const uint8_t*)image.data())
68 + y*spec.bytes_per_row);
69 uint8_t* dst = row;
70 unsigned int x, c;
71
72 for (x=0; x<spec.width; x++) {
73 c = *(src++);
74 *(dst++) = (c & spec.red_mask ) >> spec.red_shift;
75 *(dst++) = (c & spec.green_mask) >> spec.green_shift;
76 *(dst++) = (c & spec.blue_mask ) >> spec.blue_shift;
77 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
78 *(dst++) = (c & spec.alpha_mask) >> spec.alpha_shift;
79 }
80
81 png_write_rows(png, &row, 1);
82 }
83
84 png_free(png, row);
85 png_write_end(png, info);
86 png_destroy_write_struct(&png, &info);
87 return true;
88}
89
90//////////////////////////////////////////////////////////////////////
91// Functions to convert png data stored in the clipboard to a
92// clip::image.
93
94struct read_png_io {
95 const uint8_t* buf;
96 size_t len;
97 size_t pos;
98};
99
100void read_data_fn(png_structp png, png_bytep buf, png_size_t len) {
101 read_png_io& io = *(read_png_io*)png_get_io_ptr(png);
102 if (io.pos < io.len) {
103 size_t n = std::min(len, io.len-io.pos);
104 if (n > 0) {
105 std::copy(io.buf+io.pos,
106 io.buf+io.pos+n,
107 buf);
108 io.pos += n;
109 }
110 }
111}
112
113bool read_png(const uint8_t* buf,
114 const size_t len,
115 image* output_image,
116 image_spec* output_spec) {
117 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
118 nullptr, nullptr, nullptr);
119 if (!png)
120 return false;
121
122 png_infop info = png_create_info_struct(png);
123 if (!info) {
124 png_destroy_read_struct(&png, nullptr, nullptr);
125 return false;
126 }
127
128 if (setjmp(png_jmpbuf(png))) {
129 png_destroy_read_struct(&png, &info, nullptr);
130 return false;
131 }
132
133 read_png_io io = { buf, len, 0 };
134 png_set_read_fn(png, (png_voidp)&io, read_data_fn);
135
136 png_read_info(png, info);
137
138 png_uint_32 width, height;
139 int bit_depth, color_type, interlace_type;
140 png_get_IHDR(png, info, &width, &height,
141 &bit_depth, &color_type,
142 &interlace_type,
143 nullptr, nullptr);
144
145 image_spec spec;
146 spec.width = width;
147 spec.height = height;
148 spec.bits_per_pixel = 32;
149
150 // Don't use png_get_rowbytes(png, info) here because this is the
151 // bytes_per_row of the output clip::image (the png file could
152 // contain 24bpp but we want to return a 32bpp anyway with alpha=255
153 // in that case).
154 spec.bytes_per_row = 4*width;
155
156 spec.red_mask = 0x000000ff;
157 spec.green_mask = 0x0000ff00;
158 spec.blue_mask = 0x00ff0000;
159 spec.red_shift = 0;
160 spec.green_shift = 8;
161 spec.blue_shift = 16;
162
163 if ((color_type & PNG_COLOR_MASK_ALPHA) == PNG_COLOR_MASK_ALPHA) {
164 spec.alpha_mask = 0xff000000;
165 spec.alpha_shift = 24;
166 }
167 else {
168 spec.alpha_mask = 0;
169 spec.alpha_shift = 0;
170 }
171
172 if (output_spec)
173 *output_spec = spec;
174
175 if (output_image &&
176 width > 0 &&
177 height > 0) {
178 image img(spec);
179
180 // We want RGB 24-bit or RGBA 32-bit as a result
181 png_set_strip_16(png); // Down to 8-bit (TODO we might support 16-bit values)
182 png_set_packing(png); // Use one byte if color depth < 8-bit
183 png_set_expand_gray_1_2_4_to_8(png);
184 png_set_palette_to_rgb(png);
185 png_set_gray_to_rgb(png);
186 png_set_tRNS_to_alpha(png);
187
188 int number_passes = png_set_interlace_handling(png);
189 png_read_update_info(png, info);
190
191 const int src_bytes_per_row = png_get_rowbytes(png, info);
192 png_bytepp rows = (png_bytepp)png_malloc(png, sizeof(png_bytep)*height);
193 png_uint_32 y;
194 for (y=0; y<height; ++y)
195 rows[y] = (png_bytep)png_malloc(png, src_bytes_per_row);
196
197 for (int pass=0; pass<number_passes; ++pass)
198 for (y=0; y<height; ++y)
199 png_read_rows(png, rows+y, nullptr, 1);
200
201 for (y=0; y<height; ++y) {
202 const uint8_t* src = rows[y];
203 uint32_t* dst = (uint32_t*)(img.data() + y*spec.bytes_per_row);
204 unsigned int x, r, g, b, a = 0;
205
206 for (x=0; x<width; x++) {
207 r = *(src++);
208 g = *(src++);
209 b = *(src++);
210 if (spec.alpha_mask)
211 a = *(src++);
212 *(dst++) =
213 (r << spec.red_shift) |
214 (g << spec.green_shift) |
215 (b << spec.blue_shift) |
216 (a << spec.alpha_shift);
217 }
218 png_free(png, rows[y]);
219 }
220 png_free(png, rows);
221
222 std::swap(*output_image, img);
223 }
224
225 png_destroy_read_struct(&png, &info, nullptr);
226 return true;
227}
228
229} // namespace x11
230} // namespace clip
231