6 ex__icc_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_icc.c

Functions

Name
void segment(unsigned char * buf, size_t * len, unsigned marker, const unsigned char * body, size_t n)
A JPEG APP segment.
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.
size_t make_webp(unsigned char * buf, const unsigned char * profile)
A WebP shaped buffer: VP8X, ICCP (odd length, padded), VP8L.
size_t make_tiff(unsigned char * buf, const unsigned char * profile, int be)
A TIFF shaped buffer with the profile in tag 34675.
void make_profile(unsigned char * out, int ok)
A profile shaped buffer: its size, "acsp" at 36, a pattern after.
size_t make_png(unsigned char * buf, const unsigned char * profile, int after)
A PNG shaped buffer with the profile in iCCP, before or after IDAT.
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, and a start of scan.
int main(int argc, char ** argv)
run the checks, or report a file's profile
void le32(unsigned char * p, size_t v)
A little endian 32 bit value.
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.
void expect(const char * what, const unsigned char * buf, size_t len, const unsigned char * want, int expect_result)
Find a profile in a buffer, and in the same bytes as a file.
void check(const char * what, int ok)
Count and print a failed condition.
void be32(unsigned char * p, size_t v)
A big endian 32 bit value.
void add(unsigned char * buf, size_t * len, const void * data, size_t n)
Append bytes to a growing buffer.

Attributes

Name
int failures
failures counted by check()

Defines

Name
PROFILE_LEN
the test profile's length, odd so the WebP padding is exercised

Functions Documentation

function segment

static void segment(
    unsigned char * buf,
    size_t * len,
    unsigned marker,
    const unsigned char * body,
    size_t n
)

A JPEG APP segment.

Parameters:

  • buf the buffer
  • len its length, updated
  • marker the marker byte, 0xE0 to 0xEF
  • body the segment's contents
  • n their length

function png_chunk

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.

Parameters:

  • buf the buffer
  • len its length, updated
  • type the four letters
  • body the contents
  • n their length

function make_webp

static size_t make_webp(
    unsigned char * buf,
    const unsigned char * profile
)

A WebP shaped buffer: VP8X, ICCP (odd length, padded), VP8L.

Parameters:

  • buf receives it
  • profile the profile

Return: its length

function make_tiff

static size_t make_tiff(
    unsigned char * buf,
    const unsigned char * profile,
    int be
)

A TIFF shaped buffer with the profile in tag 34675.

Parameters:

  • buf receives it
  • profile the profile
  • be non-zero for big endian

Return: its length

function make_profile

static void make_profile(
    unsigned char * out,
    int ok
)

A profile shaped buffer: its size, "acsp" at 36, a pattern after.

Parameters:

  • out receives PROFILE_LEN bytes
  • ok zero to leave the signature out

function make_png

static size_t make_png(
    unsigned char * buf,
    const unsigned char * profile,
    int after
)

A PNG shaped buffer with the profile in iCCP, before or after IDAT.

Parameters:

  • buf receives it
  • profile the profile
  • after non-zero to put the iCCP after the image data, where it no longer counts

Return: its length

function make_jpeg

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, and a start of scan.

Parameters:

  • buf receives it
  • profile the profile, or NULL for none
  • chunks 2 for both chunks, 1 to leave the second out
  • padding APP1 segments of 60000 bytes before the profile

Return: its length

function main

int main(
    int argc,
    char ** argv
)

run the checks, or report a file's profile

Parameters:

  • argc argument count
  • argv arguments

Return: 0 on success

-V LEVEL is accepted and ignored, as run_tests.sh passes it to every example; it used to be taken for the name of a file.

function le32

static void le32(
    unsigned char * p,
    size_t v
)

A little endian 32 bit value.

Parameters:

  • p where
  • v the value

function icc_chunk

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.

Parameters:

  • buf the buffer
  • len its length, updated
  • seq its number
  • of how many
  • part its share of the profile
  • n that share's length

function expect

static void expect(
    const char * what,
    const unsigned char * buf,
    size_t len,
    const unsigned char * want,
    int expect_result
)

Find a profile in a buffer, and in the same bytes as a file.

Parameters:

  • what the case
  • buf the bytes
  • len how many
  • want the profile expected, or NULL for none
  • expect_result what n_icc_from_buffer() should answer

function check

static void check(
    const char * what,
    int ok
)

Count and print a failed condition.

Parameters:

  • what what was checked
  • ok the condition

function be32

static void be32(
    unsigned char * p,
    size_t v
)

A big endian 32 bit value.

Parameters:

  • p where
  • v the value

function add

static void add(
    unsigned char * buf,
    size_t * len,
    const void * data,
    size_t n
)

Append bytes to a growing buffer.

Parameters:

  • buf the buffer
  • len its length, updated
  • data what to add
  • n how many

Attributes Documentation

variable failures

static int failures = 0;

failures counted by check()

Macros Documentation

define PROFILE_LEN

#define PROFILE_LEN 301

the test profile's length, odd so the WebP padding is exercised

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_common.h"
#include "nilorea/n_icc.h"
#include "nilorea/n_log.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>

static int failures = 0;

static void check(const char* what, int ok) {
    if (!ok) {
        failures++;
        fprintf(stderr, "FAIL %s\n", what);
    }
}

#define PROFILE_LEN 301

static void make_profile(unsigned char* out, int ok) {
    int i = 0;

    for (i = 0; i < PROFILE_LEN; i++)
        out[i] = (unsigned char)(i * 7 + 3);
    out[0] = 0;
    out[1] = 0;
    out[2] = (unsigned char)(PROFILE_LEN >> 8);
    out[3] = (unsigned char)(PROFILE_LEN & 0xff);
    memcpy(out + 36, ok ? "acsp" : "nope", 4);
}

static void add(unsigned char* buf, size_t* len, const void* data, size_t n) {
    memcpy(buf + *len, data, n);
    *len += n;
}

static void segment(unsigned char* buf, size_t* len, unsigned marker, const unsigned char* body, size_t n) {
    unsigned char head[4];

    head[0] = 0xFF;
    head[1] = (unsigned char)marker;
    head[2] = (unsigned char)((n + 2) >> 8);
    head[3] = (unsigned char)((n + 2) & 0xff);
    add(buf, len, head, 4);
    add(buf, len, body, n);
}

static void icc_chunk(unsigned char* buf, size_t* len, int seq, int of, const unsigned char* part, size_t n) {
    unsigned char body[16 + PROFILE_LEN];

    memcpy(body, "ICC_PROFILE", 12);
    body[12] = (unsigned char)seq;
    body[13] = (unsigned char)of;
    memcpy(body + 14, part, n);
    segment(buf, len, 0xE2, body, n + 14);
}

static size_t make_jpeg(unsigned char* buf, const unsigned char* profile, int chunks, int padding) {
    static unsigned char filler[60000];
    const unsigned char soi[2] = {0xFF, 0xD8}, sos[4] = {0xFF, 0xDA, 0x00, 0x02};
    size_t len = 0;
    int i = 0;

    add(buf, &len, soi, 2);
    segment(buf, &len, 0xE0, (const unsigned char*)"JFIF\0\1\1\0\0\1\0\1\0\0", 14);
    for (i = 0; i < padding; i++)
        segment(buf, &len, 0xE1, filler, sizeof(filler));
    if (profile) {
        if (chunks == 2)
            icc_chunk(buf, &len, 2, 2, profile + 100, PROFILE_LEN - 100);
        icc_chunk(buf, &len, 1, 2, profile, 100);
    }
    add(buf, &len, sos, 4);
    return len;
}

static void be32(unsigned char* p, size_t v) {
    p[0] = (unsigned char)(v >> 24);
    p[1] = (unsigned char)(v >> 16);
    p[2] = (unsigned char)(v >> 8);
    p[3] = (unsigned char)v;
}

static void png_chunk(unsigned char* buf, size_t* len, const char* type, const unsigned char* body, size_t n) {
    unsigned char head[8], crc[4] = {0, 0, 0, 0};

    be32(head, n);
    memcpy(head + 4, type, 4);
    add(buf, len, head, 8);
    add(buf, len, body, n);
    add(buf, len, crc, 4);
}

static size_t make_png(unsigned char* buf, const unsigned char* profile, int after) {
    static const unsigned char sig[8] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
    unsigned char ihdr[13], iccp[64 + 2 * PROFILE_LEN], idat[4] = {0, 0, 0, 0};
    uLongf packed = 2 * PROFILE_LEN;
    size_t len = 0;

    memset(ihdr, 0, sizeof(ihdr));
    memcpy(iccp, "Test profile", 13);
    iccp[13] = 0;
    compress2(iccp + 14, &packed, profile, PROFILE_LEN, 9);
    add(buf, &len, sig, 8);
    png_chunk(buf, &len, "IHDR", ihdr, 13);
    if (!after)
        png_chunk(buf, &len, "iCCP", iccp, 14 + packed);
    png_chunk(buf, &len, "IDAT", idat, 4);
    if (after)
        png_chunk(buf, &len, "iCCP", iccp, 14 + packed);
    png_chunk(buf, &len, "IEND", idat, 0);
    return len;
}

static void le32(unsigned char* p, size_t v) {
    p[0] = (unsigned char)v;
    p[1] = (unsigned char)(v >> 8);
    p[2] = (unsigned char)(v >> 16);
    p[3] = (unsigned char)(v >> 24);
}

static size_t make_webp(unsigned char* buf, const unsigned char* profile) {
    unsigned char head[8], vp8x[10], pad = 0;
    size_t len = 0;

    memset(vp8x, 0, sizeof(vp8x));
    add(buf, &len, "RIFF\0\0\0\0WEBP", 12);
    memcpy(head, "VP8X", 4);
    le32(head + 4, sizeof(vp8x));
    add(buf, &len, head, 8);
    add(buf, &len, vp8x, sizeof(vp8x));
    memcpy(head, "ICCP", 4);
    le32(head + 4, PROFILE_LEN);
    add(buf, &len, head, 8);
    add(buf, &len, profile, PROFILE_LEN);
    add(buf, &len, &pad, 1);
    memcpy(head, "VP8L", 4);
    le32(head + 4, 0);
    add(buf, &len, head, 8);
    le32(buf + 4, len - 8);
    return len;
}

static size_t make_tiff(unsigned char* buf, const unsigned char* profile, int be) {
    /* Header, then one directory of one entry, then the profile. */
    size_t len = 8 + 2 + 12 + 4;
    int i = 0;

    memset(buf, 0, len);
    buf[0] = buf[1] = be ? 'M' : 'I';
    if (be) {
        buf[3] = 42;
        be32(buf + 4, 8);
        buf[9] = 1;
        buf[10] = 0x87;
        buf[11] = 0x73;
        buf[13] = 7;
        be32(buf + 14, PROFILE_LEN);
        be32(buf + 18, len);
    } else {
        buf[2] = 42;
        le32(buf + 4, 8);
        buf[8] = 1;
        buf[10] = 0x73;
        buf[11] = 0x87;
        buf[12] = 7;
        le32(buf + 14, PROFILE_LEN);
        le32(buf + 18, len);
    }
    for (i = 0; i < PROFILE_LEN; i++)
        buf[len + (size_t)i] = profile[i];
    return len + PROFILE_LEN;
}

static void expect(const char* what, const unsigned char* buf, size_t len, const unsigned char* want, int expect_result) {
    unsigned char* icc = NULL;
    size_t icc_len = 0;
    int got = n_icc_from_buffer(buf, len, &icc, &icc_len);
    char label[160];
    FILE* file = NULL;

    snprintf(label, sizeof(label), "%s: from a buffer", what);
    check(label, got == expect_result &&
                     (!want || (icc && icc_len == PROFILE_LEN && memcmp(icc, want, PROFILE_LEN) == 0)) &&
                     (want || !icc));
    free(icc);
    icc = NULL;
    file = fopen("ex_icc.tmp", "wb");
    if (file) {
        fwrite(buf, 1, len, file);
        fclose(file);
        got = n_icc_from_file("ex_icc.tmp", &icc, &icc_len);
        snprintf(label, sizeof(label), "%s: from a file", what);
        check(label, got == expect_result &&
                         (!want || (icc && icc_len == PROFILE_LEN && memcmp(icc, want, PROFILE_LEN) == 0)));
        free(icc);
        remove("ex_icc.tmp");
    }
}

int main(int argc, char** argv) {
    unsigned char profile[PROFILE_LEN], bad[PROFILE_LEN];
    unsigned char* buf = malloc(1024 * 1024);
    size_t len = 0;

    int opt = 0;

    set_log_level(LOG_NULL);
    if (!buf)
        return 1;
    while ((opt = getopt(argc, argv, "V:")) != -1) {
        if (opt != 'V') {
            fprintf(stderr, "Usage: %s [-V LOG_LEVEL] [file]\n", argv[0]);
            free(buf);
            return 2;
        }
    }
    if (optind < argc) {
        const char* path = argv[optind];
        unsigned char* icc = NULL;
        size_t icc_len = 0;
        int got = n_icc_from_file(path, &icc, &icc_len);

        if (got == 1)
            printf("%s: a profile of %zu bytes\n", path, icc_len);
        else
            printf("%s: %s\n", path, got == 0 ? "no profile" : "a broken profile, or unreadable");
        free(icc);
        free(buf);
        return got < 0 ? 1 : 0;
    }
    make_profile(profile, 1);
    make_profile(bad, 0);
    check("a profile is a profile", n_icc_is_profile(profile, PROFILE_LEN));
    check("one without its signature is not", !n_icc_is_profile(bad, PROFILE_LEN));
    check("nor one shorter than it says", !n_icc_is_profile(profile, PROFILE_LEN - 1));

    len = make_jpeg(buf, profile, 2, 0);
    expect("JPEG, chunks out of order", buf, len, profile, 1);
    len = make_jpeg(buf, profile, 2, 6);
    expect("JPEG, the profile past the first read", buf, len, profile, 1);
    len = make_jpeg(buf, NULL, 0, 0);
    expect("JPEG without a profile", buf, len, NULL, 0);
    len = make_jpeg(buf, profile, 1, 0);
    expect("JPEG with a chunk missing", buf, len, NULL, -1);
    len = make_jpeg(buf, profile, 2, 0);
    /* Cut inside the first chunk: only the second is whole. */
    expect("a JPEG cut short", buf, len - 60, NULL, 0);

    len = make_png(buf, profile, 0);
    expect("PNG", buf, len, profile, 1);
    len = make_png(buf, profile, 1);
    expect("PNG with iCCP after the image data", buf, len, NULL, 0);

    len = make_webp(buf, profile);
    expect("WebP", buf, len, profile, 1);

    len = make_tiff(buf, profile, 0);
    expect("TIFF little endian", buf, len, profile, 1);
    len = make_tiff(buf, profile, 1);
    expect("TIFF big endian", buf, len, profile, 1);
    len = make_tiff(buf, bad, 0);
    expect("TIFF with a broken profile", buf, len, NULL, -1);

    check("a buffer of another kind has none",
          n_icc_from_buffer((const unsigned char*)"GIF89a..", 8, &(unsigned char*){NULL}, &(size_t){0}) == 0);
    check("a missing file is an error", n_icc_from_file("/nonexistent/ex_icc", &(unsigned char*){NULL}, &(size_t){0}) == -1);

    free(buf);
    if (failures) {
        fprintf(stderr, "ex_icc: %d failure(s)\n", failures);
        return 1;
    }
    printf("ex_icc: ok\n");
    return 0;
}