Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_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
29
30#include "nilorea/n_common.h"
31#include "nilorea/n_icc.h"
32#include "nilorea/n_log.h"
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38#include <zlib.h>
39
41static int failures = 0;
42
46static void check(const char* what, int ok) {
47 if (!ok) {
48 failures++;
49 fprintf(stderr, "FAIL %s\n", what);
50 }
51}
52
54#define PROFILE_LEN 301
55
59static void make_profile(unsigned char* out, int ok) {
60 int i = 0;
61
62 for (i = 0; i < PROFILE_LEN; i++)
63 out[i] = (unsigned char)(i * 7 + 3);
64 out[0] = 0;
65 out[1] = 0;
66 out[2] = (unsigned char)(PROFILE_LEN >> 8);
67 out[3] = (unsigned char)(PROFILE_LEN & 0xff);
68 memcpy(out + 36, ok ? "acsp" : "nope", 4);
69}
70
76static void add(unsigned char* buf, size_t* len, const void* data, size_t n) {
77 memcpy(buf + *len, data, n);
78 *len += n;
79}
80
87static void segment(unsigned char* buf, size_t* len, unsigned marker, const unsigned char* body, size_t n) {
88 unsigned char head[4];
89
90 head[0] = 0xFF;
91 head[1] = (unsigned char)marker;
92 head[2] = (unsigned char)((n + 2) >> 8);
93 head[3] = (unsigned char)((n + 2) & 0xff);
94 add(buf, len, head, 4);
95 add(buf, len, body, n);
96}
97
105static void icc_chunk(unsigned char* buf, size_t* len, int seq, int of, const unsigned char* part, size_t n) {
106 unsigned char body[16 + PROFILE_LEN];
107
108 memcpy(body, "ICC_PROFILE", 12);
109 body[12] = (unsigned char)seq;
110 body[13] = (unsigned char)of;
111 memcpy(body + 14, part, n);
112 segment(buf, len, 0xE2, body, n + 14);
113}
114
122static size_t make_jpeg(unsigned char* buf, const unsigned char* profile, int chunks, int padding) {
123 static unsigned char filler[60000];
124 const unsigned char soi[2] = {0xFF, 0xD8}, sos[4] = {0xFF, 0xDA, 0x00, 0x02};
125 size_t len = 0;
126 int i = 0;
127
128 add(buf, &len, soi, 2);
129 segment(buf, &len, 0xE0, (const unsigned char*)"JFIF\0\1\1\0\0\1\0\1\0\0", 14);
130 for (i = 0; i < padding; i++)
131 segment(buf, &len, 0xE1, filler, sizeof(filler));
132 if (profile) {
133 if (chunks == 2)
134 icc_chunk(buf, &len, 2, 2, profile + 100, PROFILE_LEN - 100);
135 icc_chunk(buf, &len, 1, 2, profile, 100);
136 }
137 add(buf, &len, sos, 4);
138 return len;
139}
140
144static void be32(unsigned char* p, size_t v) {
145 p[0] = (unsigned char)(v >> 24);
146 p[1] = (unsigned char)(v >> 16);
147 p[2] = (unsigned char)(v >> 8);
148 p[3] = (unsigned char)v;
149}
150
157static void png_chunk(unsigned char* buf, size_t* len, const char* type, const unsigned char* body, size_t n) {
158 unsigned char head[8], crc[4] = {0, 0, 0, 0};
159
160 be32(head, n);
161 memcpy(head + 4, type, 4);
162 add(buf, len, head, 8);
163 add(buf, len, body, n);
164 add(buf, len, crc, 4);
165}
166
173static size_t make_png(unsigned char* buf, const unsigned char* profile, int after) {
174 static const unsigned char sig[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
175 unsigned char ihdr[13], iccp[64 + 2 * PROFILE_LEN], idat[4] = {0, 0, 0, 0};
176 uLongf packed = 2 * PROFILE_LEN;
177 size_t len = 0;
178
179 memset(ihdr, 0, sizeof(ihdr));
180 memcpy(iccp, "Test profile", 13);
181 iccp[13] = 0;
182 compress2(iccp + 14, &packed, profile, PROFILE_LEN, 9);
183 add(buf, &len, sig, 8);
184 png_chunk(buf, &len, "IHDR", ihdr, 13);
185 if (!after)
186 png_chunk(buf, &len, "iCCP", iccp, 14 + packed);
187 png_chunk(buf, &len, "IDAT", idat, 4);
188 if (after)
189 png_chunk(buf, &len, "iCCP", iccp, 14 + packed);
190 png_chunk(buf, &len, "IEND", idat, 0);
191 return len;
192}
193
197static void le32(unsigned char* p, size_t v) {
198 p[0] = (unsigned char)v;
199 p[1] = (unsigned char)(v >> 8);
200 p[2] = (unsigned char)(v >> 16);
201 p[3] = (unsigned char)(v >> 24);
202}
203
208static size_t make_webp(unsigned char* buf, const unsigned char* profile) {
209 unsigned char head[8], vp8x[10], pad = 0;
210 size_t len = 0;
211
212 memset(vp8x, 0, sizeof(vp8x));
213 add(buf, &len, "RIFF\0\0\0\0WEBP", 12);
214 memcpy(head, "VP8X", 4);
215 le32(head + 4, sizeof(vp8x));
216 add(buf, &len, head, 8);
217 add(buf, &len, vp8x, sizeof(vp8x));
218 memcpy(head, "ICCP", 4);
219 le32(head + 4, PROFILE_LEN);
220 add(buf, &len, head, 8);
221 add(buf, &len, profile, PROFILE_LEN);
222 add(buf, &len, &pad, 1);
223 memcpy(head, "VP8L", 4);
224 le32(head + 4, 0);
225 add(buf, &len, head, 8);
226 le32(buf + 4, len - 8);
227 return len;
228}
229
235static size_t make_tiff(unsigned char* buf, const unsigned char* profile, int be) {
236 /* Header, then one directory of one entry, then the profile. */
237 size_t len = 8 + 2 + 12 + 4;
238 int i = 0;
239
240 memset(buf, 0, len);
241 buf[0] = buf[1] = be ? 'M' : 'I';
242 if (be) {
243 buf[3] = 42;
244 be32(buf + 4, 8);
245 buf[9] = 1;
246 buf[10] = 0x87;
247 buf[11] = 0x73;
248 buf[13] = 7;
249 be32(buf + 14, PROFILE_LEN);
250 be32(buf + 18, len);
251 } else {
252 buf[2] = 42;
253 le32(buf + 4, 8);
254 buf[8] = 1;
255 buf[10] = 0x73;
256 buf[11] = 0x87;
257 buf[12] = 7;
258 le32(buf + 14, PROFILE_LEN);
259 le32(buf + 18, len);
260 }
261 for (i = 0; i < PROFILE_LEN; i++)
262 buf[len + (size_t)i] = profile[i];
263 return len + PROFILE_LEN;
264}
265
272static void expect(const char* what, const unsigned char* buf, size_t len, const unsigned char* want, int expect_result) {
273 unsigned char* icc = NULL;
274 size_t icc_len = 0;
275 int got = n_icc_from_buffer(buf, len, &icc, &icc_len);
276 char label[160];
277 FILE* file = NULL;
278
279 snprintf(label, sizeof(label), "%s: from a buffer", what);
280 check(label, got == expect_result &&
281 (!want || (icc && icc_len == PROFILE_LEN && memcmp(icc, want, PROFILE_LEN) == 0)) &&
282 (want || !icc));
283 free(icc);
284 icc = NULL;
285 file = fopen("ex_icc.tmp", "wb");
286 if (file) {
287 fwrite(buf, 1, len, file);
288 fclose(file);
289 got = n_icc_from_file("ex_icc.tmp", &icc, &icc_len);
290 snprintf(label, sizeof(label), "%s: from a file", what);
291 check(label, got == expect_result &&
292 (!want || (icc && icc_len == PROFILE_LEN && memcmp(icc, want, PROFILE_LEN) == 0)));
293 free(icc);
294 remove("ex_icc.tmp");
295 }
296}
297
306int main(int argc, char** argv) {
307 unsigned char profile[PROFILE_LEN], bad[PROFILE_LEN];
308 unsigned char* buf = malloc(1024 * 1024);
309 size_t len = 0;
310
311 int opt = 0;
312
314 if (!buf)
315 return 1;
316 while ((opt = getopt(argc, argv, "V:")) != -1) {
317 if (opt != 'V') {
318 fprintf(stderr, "Usage: %s [-V LOG_LEVEL] [file]\n", argv[0]);
319 free(buf);
320 return 2;
321 }
322 }
323 if (optind < argc) {
324 const char* path = argv[optind];
325 unsigned char* icc = NULL;
326 size_t icc_len = 0;
327 int got = n_icc_from_file(path, &icc, &icc_len);
328
329 if (got == 1)
330 printf("%s: a profile of %zu bytes\n", path, icc_len);
331 else
332 printf("%s: %s\n", path, got == 0 ? "no profile" : "a broken profile, or unreadable");
333 free(icc);
334 free(buf);
335 return got < 0 ? 1 : 0;
336 }
337 make_profile(profile, 1);
338 make_profile(bad, 0);
339 check("a profile is a profile", n_icc_is_profile(profile, PROFILE_LEN));
340 check("one without its signature is not", !n_icc_is_profile(bad, PROFILE_LEN));
341 check("nor one shorter than it says", !n_icc_is_profile(profile, PROFILE_LEN - 1));
342
343 len = make_jpeg(buf, profile, 2, 0);
344 expect("JPEG, chunks out of order", buf, len, profile, 1);
345 len = make_jpeg(buf, profile, 2, 6);
346 expect("JPEG, the profile past the first read", buf, len, profile, 1);
347 len = make_jpeg(buf, NULL, 0, 0);
348 expect("JPEG without a profile", buf, len, NULL, 0);
349 len = make_jpeg(buf, profile, 1, 0);
350 expect("JPEG with a chunk missing", buf, len, NULL, -1);
351 len = make_jpeg(buf, profile, 2, 0);
352 /* Cut inside the first chunk: only the second is whole. */
353 expect("a JPEG cut short", buf, len - 60, NULL, 0);
354
355 len = make_png(buf, profile, 0);
356 expect("PNG", buf, len, profile, 1);
357 len = make_png(buf, profile, 1);
358 expect("PNG with iCCP after the image data", buf, len, NULL, 0);
359
360 len = make_webp(buf, profile);
361 expect("WebP", buf, len, profile, 1);
362
363 len = make_tiff(buf, profile, 0);
364 expect("TIFF little endian", buf, len, profile, 1);
365 len = make_tiff(buf, profile, 1);
366 expect("TIFF big endian", buf, len, profile, 1);
367 len = make_tiff(buf, bad, 0);
368 expect("TIFF with a broken profile", buf, len, NULL, -1);
369
370 check("a buffer of another kind has none",
371 n_icc_from_buffer((const unsigned char*)"GIF89a..", 8, &(unsigned char*){NULL}, &(size_t){0}) == 0);
372 check("a missing file is an error", n_icc_from_file("/nonexistent/ex_icc", &(unsigned char*){NULL}, &(size_t){0}) == -1);
373
374 free(buf);
375 if (failures) {
376 fprintf(stderr, "ex_icc: %d failure(s)\n", failures);
377 return 1;
378 }
379 printf("ex_icc: ok\n");
380 return 0;
381}
static int expect(BITMAP *bmp, int x, int y, int r, int g, int b, const char *what)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static unsigned char * make_png(int w, int h, size_t *out_len)
Definition ex_atlas.c:76
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
static void make_profile(unsigned char *out, int ok)
A profile shaped buffer: its size, "acsp" at 36, a pattern after.
Definition ex_icc.c:59
static size_t make_tiff(unsigned char *buf, const unsigned char *profile, int be)
A TIFF shaped buffer with the profile in tag 34675.
Definition ex_icc.c:235
static void png_chunk(unsigned char *buf, size_t *len, const char *type, const unsigned char *body, size_t n)
A PNG chunk, CRC left zero: nothing here checks it.
Definition ex_icc.c:157
static void segment(unsigned char *buf, size_t *len, unsigned marker, const unsigned char *body, size_t n)
A JPEG APP segment.
Definition ex_icc.c:87
static void icc_chunk(unsigned char *buf, size_t *len, int seq, int of, const unsigned char *part, size_t n)
One ICC chunk of a JPEG.
Definition ex_icc.c:105
static void be32(unsigned char *p, size_t v)
A big endian 32 bit value.
Definition ex_icc.c:144
#define PROFILE_LEN
the test profile's length, odd so the WebP padding is exercised
Definition ex_icc.c:54
static size_t make_webp(unsigned char *buf, const unsigned char *profile)
A WebP shaped buffer: VP8X, ICCP (odd length, padded), VP8L.
Definition ex_icc.c:208
static void add(unsigned char *buf, size_t *len, const void *data, size_t n)
Append bytes to a growing buffer.
Definition ex_icc.c:76
static void check(const char *what, int ok)
Count and print a failed condition.
Definition ex_icc.c:46
static size_t make_jpeg(unsigned char *buf, const unsigned char *profile, int chunks, int padding)
A JPEG shaped buffer: SOI, an APP0, padding segments, the profile in two chunks, second first,...
Definition ex_icc.c:122
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NULL
no log output
Definition n_log.h:70
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
Common headers and low-level functions & define.
Finding the ICC colour profile embedded in an image file.
Generic log system.
static uint32_t le32(const unsigned char *p)
helper: a little endian 32 bit value
Definition n_zip.c:49