Table of Contents
- src/n_icc.c
- Functions
- Attributes
- Defines
- Detailed Description
- Functions Documentation
- function _tiff_file
- function _le32
- function _give
- function _from_webp
- function _from_tiff
- function _from_png
- function _from_jpeg
- function _from_buffer
- function _be32
- function _be16
- Attributes Documentation
- Macros Documentation
- Source code
src/n_icc.c
Finding the ICC colour profile embedded in an image file. More...
Functions
| Name | |
|---|---|
| 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 to find a profile at its end |
| size_t | _le32(const unsigned char * p) helper: a little endian 32 bit value |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| size_t | _be32(const unsigned char * p) helper: a big endian 32 bit value |
| size_t | _be16(const unsigned char * p) helper: a big endian 16 bit value |
Attributes
| Name | |
|---|---|
| const char[12] | n_icc_jpeg_id JPEG APP2 identifier of a profile chunk, terminator included. |
Defines
| Name | |
|---|---|
| N_ICC_TIFF_TAG TIFF tag of an embedded ICC profile. |
|
| 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 reader then reads more. |
|
| N_ICC_MIN_BYTES Smallest ICC profile: its 128 byte header and a tag count. |
|
| N_ICC_FIRST_READ Bytes n_icc_from_file() reads first; a profile, when there is one, is almost always in them. |
Detailed Description
Finding the ICC colour profile embedded in an image file.
Author: Castagnier Mickael
Version: 1.0
Date: 28/09/2026
Functions Documentation
function _tiff_file
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 to find a profile at its end
function _le32
static size_t _le32(
const unsigned char * p
)
helper: a little endian 32 bit value
function _give
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
Return: 1, or -1 when it is not a profile or there is no memory
function _from_webp
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
function _from_tiff
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
function _from_png
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
function _from_jpeg
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
function _from_buffer
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
function _be32
static size_t _be32(
const unsigned char * p
)
helper: a big endian 32 bit value
function _be16
static size_t _be16(
const unsigned char * p
)
helper: a big endian 16 bit value
Attributes Documentation
variable n_icc_jpeg_id
static const char[12] n_icc_jpeg_id = {'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', 0};
JPEG APP2 identifier of a profile chunk, terminator included.
Macros Documentation
define N_ICC_TIFF_TAG
#define N_ICC_TIFF_TAG 34675
TIFF tag of an embedded ICC profile.
define N_ICC_MORE
#define N_ICC_MORE 2
What a parser answers when it ran off the end of the bytes it was given before it could say: the file reader then reads more.
define N_ICC_MIN_BYTES
#define N_ICC_MIN_BYTES 132u
Smallest ICC profile: its 128 byte header and a tag count.
define N_ICC_FIRST_READ
#define N_ICC_FIRST_READ (256u * 1024u)
Bytes n_icc_from_file() reads first; a profile, when there is one, is almost always in them.
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "nilorea/n_icc.h"
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_tiff.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#define N_ICC_MIN_BYTES 132u
#define N_ICC_MORE 2
#define N_ICC_FIRST_READ (256u * 1024u)
#define N_ICC_TIFF_TAG 34675
static const char n_icc_jpeg_id[12] = {'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', 0};
static size_t _be16(const unsigned char* p) {
return ((size_t)p[0] << 8) | (size_t)p[1];
}
static size_t _be32(const unsigned char* p) {
return ((size_t)p[0] << 24) | ((size_t)p[1] << 16) | ((size_t)p[2] << 8) | (size_t)p[3];
}
static size_t _le32(const unsigned char* p) {
return ((size_t)p[3] << 24) | ((size_t)p[2] << 16) | ((size_t)p[1] << 8) | (size_t)p[0];
}
int n_icc_is_profile(const unsigned char* data, size_t len) {
size_t declared = 0;
if (!data || len < N_ICC_MIN_BYTES)
return 0;
declared = _be32(data);
if (declared < N_ICC_MIN_BYTES || declared > len)
return 0;
return memcmp(data + 36, "acsp", 4) == 0;
}
static int _give(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
unsigned char* copy = NULL;
if (len > N_ICC_MAX_BYTES || !n_icc_is_profile(data, len)) {
n_log(LOG_ERR, "icc: an embedded profile of %zu bytes is not a valid profile", len);
return -1;
}
copy = malloc(len);
if (!copy)
return -1;
memcpy(copy, data, len);
*icc = copy;
*icc_len = len;
return 1;
}
static int _from_jpeg(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
/* Where each numbered chunk is: numbers run from 1 to the count. */
const unsigned char* chunk[256];
size_t chunk_len[256];
size_t at = 2, total = 0;
int count = 0, seen = 0, n = 0;
unsigned char* out = NULL;
int complete = 0;
memset(chunk, 0, sizeof(chunk));
memset(chunk_len, 0, sizeof(chunk_len));
while (at + 4 <= len) {
unsigned marker = 0;
size_t seglen = 0;
if (data[at] != 0xFF) {
/* Not a marker where one must be: nothing more can be read. */
complete = 1;
break;
}
/* Any number of fill bytes may come before a marker. */
while (at < len && data[at] == 0xFF)
at++;
if (at >= len)
break;
marker = data[at++];
/* Start of scan or end of image: the profile comes before both. */
if (marker == 0xDA || marker == 0xD9) {
complete = 1;
break;
}
/* Markers with no length. */
if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7))
continue;
if (at + 2 > len)
break;
seglen = _be16(data + at);
if (seglen < 2) {
complete = 1;
break;
}
if (at + seglen > len)
break;
if (marker == 0xE2 && seglen >= 2 + 12 + 2 && memcmp(data + at + 2, n_icc_jpeg_id, 12) == 0) {
int seq = data[at + 14], of = data[at + 15];
if (seq < 1 || of < 1 || seq > of || (count && of != count) || chunk[seq]) {
n_log(LOG_ERR, "icc: JPEG profile chunks do not agree");
return -1;
}
count = of;
chunk[seq] = data + at + 16;
chunk_len[seq] = seglen - 16;
total += chunk_len[seq];
seen++;
}
at += seglen;
}
if (!complete && seen != count)
return N_ICC_MORE;
if (!seen)
return complete ? 0 : N_ICC_MORE;
if (seen != count || total > N_ICC_MAX_BYTES) {
n_log(LOG_ERR, "icc: JPEG profile has %d of %d chunks", seen, count);
return -1;
}
out = malloc(total ? total : 1);
if (!out)
return -1;
total = 0;
for (n = 1; n <= count; n++) {
memcpy(out + total, chunk[n], chunk_len[n]);
total += chunk_len[n];
}
if (!n_icc_is_profile(out, total)) {
n_log(LOG_ERR, "icc: the JPEG's profile chunks do not make a profile");
free(out);
return -1;
}
*icc = out;
*icc_len = total;
return 1;
}
static int _from_png(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
size_t at = 8;
while (at + 12 <= len) {
size_t clen = _be32(data + at);
const unsigned char* type = data + at + 4;
const unsigned char* body = data + at + 8;
if (clen > len - at - 12)
return N_ICC_MORE;
if (memcmp(type, "IDAT", 4) == 0 || memcmp(type, "IEND", 4) == 0)
return 0;
if (memcmp(type, "iCCP", 4) == 0) {
size_t name = 0;
z_stream zip;
unsigned char* out = NULL;
size_t room = 64u * 1024u;
int zerr = Z_OK;
/* A name of 1 to 79 characters, its terminator, and the
* compression method, which must be 0, deflate. */
while (name < clen && name < 80 && body[name])
name++;
if (name == 0 || name >= 80 || name + 2 > clen || body[name + 1] != 0) {
n_log(LOG_ERR, "icc: a PNG iCCP chunk with a broken header");
return -1;
}
memset(&zip, 0, sizeof(zip));
if (inflateInit(&zip) != Z_OK)
return -1;
out = malloc(room);
if (!out) {
inflateEnd(&zip);
return -1;
}
zip.next_in = (unsigned char*)(body + name + 2);
zip.avail_in = (unsigned)(clen - name - 2);
while (zerr == Z_OK) {
if (zip.total_out == room) {
unsigned char* more = NULL;
if (room >= N_ICC_MAX_BYTES)
break;
more = realloc(out, room * 2u);
if (!more)
break;
out = more;
room *= 2u;
}
zip.next_out = out + zip.total_out;
zip.avail_out = (unsigned)(room - zip.total_out);
zerr = inflate(&zip, Z_NO_FLUSH);
}
inflateEnd(&zip);
if (zerr != Z_STREAM_END || !n_icc_is_profile(out, zip.total_out)) {
n_log(LOG_ERR, "icc: a PNG iCCP chunk that does not inflate to a profile");
free(out);
return -1;
}
*icc = out;
*icc_len = zip.total_out;
return 1;
}
at += 12 + clen;
}
return N_ICC_MORE;
}
static int _from_webp(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
size_t at = 12;
while (at + 8 <= len) {
size_t clen = _le32(data + at + 4);
if (clen > len - at - 8)
return N_ICC_MORE;
/* The image data: an ICCP chunk comes before it. */
if (memcmp(data + at, "VP8 ", 4) == 0 || memcmp(data + at, "VP8L", 4) == 0 ||
memcmp(data + at, "ANIM", 4) == 0)
return 0;
if (memcmp(data + at, "ICCP", 4) == 0)
return _give(data + at + 8, clen, icc, icc_len);
/* Chunks are padded to an even length. */
at += 8 + clen + (clen & 1u);
}
return N_ICC_MORE;
}
static int _from_tiff(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
N_TIFF* tiff = n_tiff_parse(data, len);
const N_TIFF_ENTRY* entry = NULL;
int result = 0;
if (!tiff)
return 0;
entry = n_tiff_find(tiff, N_ICC_TIFF_TAG);
/* UNDEFINED bytes, or BYTE as some writers put it. */
if (entry && (entry->type == 7 || entry->type == 1))
result = _give(data + entry->offset, entry->count, icc, icc_len);
n_tiff_free(&tiff);
return result;
}
static int _from_buffer(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
static const unsigned char png_sig[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
*icc = NULL;
*icc_len = 0;
if (len >= 4 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF)
return _from_jpeg(data, len, icc, icc_len);
if (len >= 8 && memcmp(data, png_sig, 8) == 0)
return _from_png(data, len, icc, icc_len);
if (len >= 12 && memcmp(data, "RIFF", 4) == 0 && memcmp(data + 8, "WEBP", 4) == 0)
return _from_webp(data, len, icc, icc_len);
if (len >= 8 && ((data[0] == 'I' && data[1] == 'I') || (data[0] == 'M' && data[1] == 'M')))
return _from_tiff(data, len, icc, icc_len);
return 0;
}
int n_icc_from_buffer(const unsigned char* data, size_t len, unsigned char** icc, size_t* icc_len) {
int result = 0;
__n_assert(data, return -1);
__n_assert(icc, return -1);
__n_assert(icc_len, return -1);
result = _from_buffer(data, len, icc, icc_len);
/* The whole file was given: running off its end is having none. */
return result == N_ICC_MORE ? 0 : result;
}
static int _tiff_file(FILE* file, unsigned char** icc, size_t* icc_len) {
unsigned char header[8], entry[12];
int big = 0;
size_t ifd = 0, count = 0, i = 0;
if (fseek(file, 0, SEEK_SET) != 0 || fread(header, 1, 8, file) != 8)
return 0;
big = header[0] == 'M';
ifd = big ? _be32(header + 4) : _le32(header + 4);
if (fseek(file, (long)ifd, SEEK_SET) != 0 || fread(entry, 1, 2, file) != 2)
return 0;
count = big ? _be16(entry) : (size_t)entry[0] | ((size_t)entry[1] << 8);
if (count > 4096)
return 0;
for (i = 0; i < count; i++) {
size_t tag = 0, type = 0, n = 0, offset = 0;
unsigned char* out = NULL;
if (fread(entry, 1, 12, file) != 12)
return 0;
tag = big ? _be16(entry) : (size_t)entry[0] | ((size_t)entry[1] << 8);
if (tag != N_ICC_TIFF_TAG)
continue;
type = big ? _be16(entry + 2) : (size_t)entry[2] | ((size_t)entry[3] << 8);
n = big ? _be32(entry + 4) : _le32(entry + 4);
offset = big ? _be32(entry + 8) : _le32(entry + 8);
if ((type != 7 && type != 1) || n < N_ICC_MIN_BYTES || n > N_ICC_MAX_BYTES)
return -1;
out = malloc(n);
if (!out)
return -1;
if (fseek(file, (long)offset, SEEK_SET) != 0 || fread(out, 1, n, file) != n ||
!n_icc_is_profile(out, n)) {
n_log(LOG_ERR, "icc: the TIFF's profile tag does not hold a profile");
free(out);
return -1;
}
*icc = out;
*icc_len = n;
return 1;
}
return 0;
}
int n_icc_from_file(const char* path, unsigned char** icc, size_t* icc_len) {
FILE* file = NULL;
unsigned char* head = NULL;
size_t got = 0, room = N_ICC_FIRST_READ;
int result = 0;
__n_assert(path, return -1);
__n_assert(icc, return -1);
__n_assert(icc_len, return -1);
*icc = NULL;
*icc_len = 0;
file = fopen(path, "rb");
if (!file)
return -1;
/* A little first, and more only while the parser runs off the end of
* what it has: most files answer in the first read, and the thumbnail
* workers ask for thousands of them. */
for (;;) {
unsigned char* more = realloc(head, room);
if (!more) {
result = -1;
break;
}
head = more;
got += fread(head + got, 1, room - got, file);
int at_end = got < room;
if (got >= 8 && ((head[0] == 'I' && head[1] == 'I') || (head[0] == 'M' && head[1] == 'M'))) {
result = _tiff_file(file, icc, icc_len);
break;
}
result = _from_buffer(head, got, icc, icc_len);
if (result != N_ICC_MORE)
break;
if (at_end || room >= N_ICC_HEAD_BYTES) {
result = 0;
break;
}
room = room * 4u > N_ICC_HEAD_BYTES ? N_ICC_HEAD_BYTES : room * 4u;
}
free(head);
fclose(file);
return result;
}
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB