Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_icc.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27
28#include "nilorea/n_icc.h"
29#include "nilorea/n_common.h"
30#include "nilorea/n_log.h"
31#include "nilorea/n_tiff.h"
32
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <zlib.h>
37
39#define N_ICC_MIN_BYTES 132u
40
43#define N_ICC_MORE 2
44
47#define N_ICC_FIRST_READ (256u * 1024u)
48
50#define N_ICC_TIFF_TAG 34675
51
53static const char n_icc_jpeg_id[12] = {'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', 0};
54
56static size_t _be16(const unsigned char* p) {
57 return ((size_t)p[0] << 8) | (size_t)p[1];
58}
59
61static size_t _be32(const unsigned char* p) {
62 return ((size_t)p[0] << 24) | ((size_t)p[1] << 16) | ((size_t)p[2] << 8) | (size_t)p[3];
63}
64
66static size_t _le32(const unsigned char* p) {
67 return ((size_t)p[3] << 24) | ((size_t)p[2] << 16) | ((size_t)p[1] << 8) | (size_t)p[0];
68}
69
70int n_icc_is_profile(const unsigned char* data, size_t len) {
71 size_t declared = 0;
72
73 if (!data || len < N_ICC_MIN_BYTES)
74 return 0;
75 declared = _be32(data);
76 if (declared < N_ICC_MIN_BYTES || declared > len)
77 return 0;
78 return memcmp(data + 36, "acsp", 4) == 0;
79}
80
83static int _give(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
84 unsigned char* copy = NULL;
85
86 if (len > N_ICC_MAX_BYTES || !n_icc_is_profile(data, len)) {
87 n_log(LOG_ERR, "icc: an embedded profile of %zu bytes is not a valid profile", len);
88 return -1;
89 }
90 copy = malloc(len);
91 if (!copy)
92 return -1;
93 memcpy(copy, data, len);
94 *icc = copy;
95 *icc_len = len;
96 return 1;
97}
98
100static int _from_jpeg(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
101 /* Where each numbered chunk is: numbers run from 1 to the count. */
102 const unsigned char* chunk[256];
103 size_t chunk_len[256];
104 size_t at = 2, total = 0;
105 int count = 0, seen = 0, n = 0;
106 unsigned char* out = NULL;
107
108 int complete = 0;
109
110 memset(chunk, 0, sizeof(chunk));
111 memset(chunk_len, 0, sizeof(chunk_len));
112 while (at + 4 <= len) {
113 unsigned marker = 0;
114 size_t seglen = 0;
115
116 if (data[at] != 0xFF) {
117 /* Not a marker where one must be: nothing more can be read. */
118 complete = 1;
119 break;
120 }
121 /* Any number of fill bytes may come before a marker. */
122 while (at < len && data[at] == 0xFF)
123 at++;
124 if (at >= len)
125 break;
126 marker = data[at++];
127 /* Start of scan or end of image: the profile comes before both. */
128 if (marker == 0xDA || marker == 0xD9) {
129 complete = 1;
130 break;
131 }
132 /* Markers with no length. */
133 if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7))
134 continue;
135 if (at + 2 > len)
136 break;
137 seglen = _be16(data + at);
138 if (seglen < 2) {
139 complete = 1;
140 break;
141 }
142 if (at + seglen > len)
143 break;
144 if (marker == 0xE2 && seglen >= 2 + 12 + 2 && memcmp(data + at + 2, n_icc_jpeg_id, 12) == 0) {
145 int seq = data[at + 14], of = data[at + 15];
146
147 if (seq < 1 || of < 1 || seq > of || (count && of != count) || chunk[seq]) {
148 n_log(LOG_ERR, "icc: JPEG profile chunks do not agree");
149 return -1;
150 }
151 count = of;
152 chunk[seq] = data + at + 16;
153 chunk_len[seq] = seglen - 16;
154 total += chunk_len[seq];
155 seen++;
156 }
157 at += seglen;
158 }
159 if (!complete && seen != count)
160 return N_ICC_MORE;
161 if (!seen)
162 return complete ? 0 : N_ICC_MORE;
163 if (seen != count || total > N_ICC_MAX_BYTES) {
164 n_log(LOG_ERR, "icc: JPEG profile has %d of %d chunks", seen, count);
165 return -1;
166 }
167 out = malloc(total ? total : 1);
168 if (!out)
169 return -1;
170 total = 0;
171 for (n = 1; n <= count; n++) {
172 memcpy(out + total, chunk[n], chunk_len[n]);
173 total += chunk_len[n];
174 }
175 if (!n_icc_is_profile(out, total)) {
176 n_log(LOG_ERR, "icc: the JPEG's profile chunks do not make a profile");
177 free(out);
178 return -1;
179 }
180 *icc = out;
181 *icc_len = total;
182 return 1;
183}
184
186static int _from_png(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
187 size_t at = 8;
188
189 while (at + 12 <= len) {
190 size_t clen = _be32(data + at);
191 const unsigned char* type = data + at + 4;
192 const unsigned char* body = data + at + 8;
193
194 if (clen > len - at - 12)
195 return N_ICC_MORE;
196 if (memcmp(type, "IDAT", 4) == 0 || memcmp(type, "IEND", 4) == 0)
197 return 0;
198 if (memcmp(type, "iCCP", 4) == 0) {
199 size_t name = 0;
200 z_stream zip;
201 unsigned char* out = NULL;
202 size_t room = 64u * 1024u;
203 int zerr = Z_OK;
204
205 /* A name of 1 to 79 characters, its terminator, and the
206 * compression method, which must be 0, deflate. */
207 while (name < clen && name < 80 && body[name])
208 name++;
209 if (name == 0 || name >= 80 || name + 2 > clen || body[name + 1] != 0) {
210 n_log(LOG_ERR, "icc: a PNG iCCP chunk with a broken header");
211 return -1;
212 }
213 memset(&zip, 0, sizeof(zip));
214 if (inflateInit(&zip) != Z_OK)
215 return -1;
216 out = malloc(room);
217 if (!out) {
218 inflateEnd(&zip);
219 return -1;
220 }
221 zip.next_in = (unsigned char*)(body + name + 2);
222 zip.avail_in = (unsigned)(clen - name - 2);
223 while (zerr == Z_OK) {
224 if (zip.total_out == room) {
225 unsigned char* more = NULL;
226
227 if (room >= N_ICC_MAX_BYTES)
228 break;
229 more = realloc(out, room * 2u);
230 if (!more)
231 break;
232 out = more;
233 room *= 2u;
234 }
235 zip.next_out = out + zip.total_out;
236 zip.avail_out = (unsigned)(room - zip.total_out);
237 zerr = inflate(&zip, Z_NO_FLUSH);
238 }
239 inflateEnd(&zip);
240 if (zerr != Z_STREAM_END || !n_icc_is_profile(out, zip.total_out)) {
241 n_log(LOG_ERR, "icc: a PNG iCCP chunk that does not inflate to a profile");
242 free(out);
243 return -1;
244 }
245 *icc = out;
246 *icc_len = zip.total_out;
247 return 1;
248 }
249 at += 12 + clen;
250 }
251 return N_ICC_MORE;
252}
253
255static int _from_webp(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
256 size_t at = 12;
257
258 while (at + 8 <= len) {
259 size_t clen = _le32(data + at + 4);
260
261 if (clen > len - at - 8)
262 return N_ICC_MORE;
263 /* The image data: an ICCP chunk comes before it. */
264 if (memcmp(data + at, "VP8 ", 4) == 0 || memcmp(data + at, "VP8L", 4) == 0 ||
265 memcmp(data + at, "ANIM", 4) == 0)
266 return 0;
267 if (memcmp(data + at, "ICCP", 4) == 0)
268 return _give(data + at + 8, clen, icc, icc_len);
269 /* Chunks are padded to an even length. */
270 at += 8 + clen + (clen & 1u);
271 }
272 return N_ICC_MORE;
273}
274
276static int _from_tiff(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
277 N_TIFF* tiff = n_tiff_parse(data, len);
278 const N_TIFF_ENTRY* entry = NULL;
279 int result = 0;
280
281 if (!tiff)
282 return 0;
283 entry = n_tiff_find(tiff, N_ICC_TIFF_TAG);
284 /* UNDEFINED bytes, or BYTE as some writers put it. */
285 if (entry && (entry->type == 7 || entry->type == 1))
286 result = _give(data + entry->offset, entry->count, icc, icc_len);
287 n_tiff_free(&tiff);
288 return result;
289}
290
293static int _from_buffer(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
294 static const unsigned char png_sig[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
295
296 *icc = NULL;
297 *icc_len = 0;
298 if (len >= 4 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF)
299 return _from_jpeg(data, len, icc, icc_len);
300 if (len >= 8 && memcmp(data, png_sig, 8) == 0)
301 return _from_png(data, len, icc, icc_len);
302 if (len >= 12 && memcmp(data, "RIFF", 4) == 0 && memcmp(data + 8, "WEBP", 4) == 0)
303 return _from_webp(data, len, icc, icc_len);
304 if (len >= 8 && ((data[0] == 'I' && data[1] == 'I') || (data[0] == 'M' && data[1] == 'M')))
305 return _from_tiff(data, len, icc, icc_len);
306 return 0;
307}
308
309int n_icc_from_buffer(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
310 int result = 0;
311
312 __n_assert(data, return -1);
313 __n_assert(icc, return -1);
314 __n_assert(icc_len, return -1);
315 result = _from_buffer(data, len, icc, icc_len);
316 /* The whole file was given: running off its end is having none. */
317 return result == N_ICC_MORE ? 0 : result;
318}
319
322static int _tiff_file(FILE* file, unsigned char** icc, size_t* icc_len) {
323 unsigned char header[8], entry[12];
324 int big = 0;
325 size_t ifd = 0, count = 0, i = 0;
326
327 if (fseek(file, 0, SEEK_SET) != 0 || fread(header, 1, 8, file) != 8)
328 return 0;
329 big = header[0] == 'M';
330 ifd = big ? _be32(header + 4) : _le32(header + 4);
331 if (fseek(file, (long)ifd, SEEK_SET) != 0 || fread(entry, 1, 2, file) != 2)
332 return 0;
333 count = big ? _be16(entry) : (size_t)entry[0] | ((size_t)entry[1] << 8);
334 if (count > 4096)
335 return 0;
336 for (i = 0; i < count; i++) {
337 size_t tag = 0, type = 0, n = 0, offset = 0;
338 unsigned char* out = NULL;
339
340 if (fread(entry, 1, 12, file) != 12)
341 return 0;
342 tag = big ? _be16(entry) : (size_t)entry[0] | ((size_t)entry[1] << 8);
343 if (tag != N_ICC_TIFF_TAG)
344 continue;
345 type = big ? _be16(entry + 2) : (size_t)entry[2] | ((size_t)entry[3] << 8);
346 n = big ? _be32(entry + 4) : _le32(entry + 4);
347 offset = big ? _be32(entry + 8) : _le32(entry + 8);
348 if ((type != 7 && type != 1) || n < N_ICC_MIN_BYTES || n > N_ICC_MAX_BYTES)
349 return -1;
350 out = malloc(n);
351 if (!out)
352 return -1;
353 if (fseek(file, (long)offset, SEEK_SET) != 0 || fread(out, 1, n, file) != n ||
354 !n_icc_is_profile(out, n)) {
355 n_log(LOG_ERR, "icc: the TIFF's profile tag does not hold a profile");
356 free(out);
357 return -1;
358 }
359 *icc = out;
360 *icc_len = n;
361 return 1;
362 }
363 return 0;
364}
365
366int n_icc_from_file(const char* path, unsigned char** icc, size_t* icc_len) {
367 FILE* file = NULL;
368 unsigned char* head = NULL;
369 size_t got = 0, room = N_ICC_FIRST_READ;
370 int result = 0;
371
372 __n_assert(path, return -1);
373 __n_assert(icc, return -1);
374 __n_assert(icc_len, return -1);
375 *icc = NULL;
376 *icc_len = 0;
377 file = fopen(path, "rb");
378 if (!file)
379 return -1;
380 /* A little first, and more only while the parser runs off the end of
381 * what it has: most files answer in the first read, and the thumbnail
382 * workers ask for thousands of them. */
383 for (;;) {
384 unsigned char* more = realloc(head, room);
385
386 if (!more) {
387 result = -1;
388 break;
389 }
390 head = more;
391 got += fread(head + got, 1, room - got, file);
392 int at_end = got < room;
393 if (got >= 8 && ((head[0] == 'I' && head[1] == 'I') || (head[0] == 'M' && head[1] == 'M'))) {
394 result = _tiff_file(file, icc, icc_len);
395 break;
396 }
397 result = _from_buffer(head, got, icc, icc_len);
398 if (result != N_ICC_MORE)
399 break;
400 if (at_end || room >= N_ICC_HEAD_BYTES) {
401 result = 0;
402 break;
403 }
404 room = room * 4u > N_ICC_HEAD_BYTES ? N_ICC_HEAD_BYTES : room * 4u;
405 }
406 free(head);
407 fclose(file);
408 return result;
409}
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
#define N_ICC_MAX_BYTES
Largest profile returned.
Definition n_icc.h:63
#define N_ICC_HEAD_BYTES
Bytes of a file n_icc_from_file() reads to look for a JPEG, PNG or WebP profile, which those formats ...
Definition n_icc.h:67
int n_icc_from_file(const char *path, unsigned char **icc, size_t *icc_len)
Find the profile embedded in an image file.
Definition n_icc.c:366
int n_icc_from_buffer(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
Find the profile embedded in an image file held in memory.
Definition n_icc.c:309
int n_icc_is_profile(const unsigned char *data, size_t len)
Does a buffer look like an ICC profile: at least a header, a size field no larger than the buffer,...
Definition n_icc.c:70
size_t offset
where the values start in the buffer, already checked to hold them
Definition n_tiff.h:74
uint16_t type
TIFF field type, 1 BYTE to 12 DOUBLE.
Definition n_tiff.h:70
uint32_t count
number of values
Definition n_tiff.h:72
N_TIFF * n_tiff_parse(const unsigned char *data, size_t len)
Parse the first directory of a TIFF structured buffer.
Definition n_tiff.c:94
const N_TIFF_ENTRY * n_tiff_find(const N_TIFF *tiff, uint16_t tag)
Find an entry by tag.
Definition n_tiff.c:167
void n_tiff_free(N_TIFF **tiff)
Release a parse, and set the pointer to NULL.
Definition n_tiff.c:160
The first directory of a TIFF structured buffer.
Definition n_tiff.h:78
One directory entry.
Definition n_tiff.h:66
Common headers and low-level functions & define.
static int _from_webp(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: the profile of a WebP, from its ICCP chunk
Definition n_icc.c:255
static size_t _le32(const unsigned char *p)
helper: a little endian 32 bit value
Definition n_icc.c:66
static int _from_buffer(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: n_icc_from_buffer(), with N_ICC_MORE when the bytes ran out before the answer
Definition n_icc.c:293
static int _from_tiff(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: the profile of a TIFF held whole in memory
Definition n_icc.c:276
static int _from_png(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: the profile of a PNG, from its iCCP chunk, inflated
Definition n_icc.c:186
static size_t _be16(const unsigned char *p)
helper: a big endian 16 bit value
Definition n_icc.c:56
#define N_ICC_TIFF_TAG
TIFF tag of an embedded ICC profile.
Definition n_icc.c:50
static const char n_icc_jpeg_id[12]
JPEG APP2 identifier of a profile chunk, terminator included.
Definition n_icc.c:53
static int _from_jpeg(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: the profile of a JPEG, from its APP2 chunks put back in order
Definition n_icc.c:100
#define N_ICC_FIRST_READ
Bytes n_icc_from_file() reads first; a profile, when there is one, is almost always in them.
Definition n_icc.c:47
#define N_ICC_MORE
What a parser answers when it ran off the end of the bytes it was given before it could say: the file...
Definition n_icc.c:43
static int _give(const unsigned char *data, size_t len, unsigned char **icc, size_t *icc_len)
helper: hand a found profile back, a copy of len bytes at data, checked
Definition n_icc.c:83
static int _tiff_file(FILE *file, unsigned char **icc, size_t *icc_len)
helper: read the TIFF profile of a file where its directory says, so a large TIFF is not read whole t...
Definition n_icc.c:322
#define N_ICC_MIN_BYTES
Smallest ICC profile: its 128 byte header and a tag count.
Definition n_icc.c:39
static size_t _be32(const unsigned char *p)
helper: a big endian 32 bit value
Definition n_icc.c:61
Finding the ICC colour profile embedded in an image file.
Generic log system.
Reading the tags of a TIFF structured buffer.