mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-10-06 01:12:40 +02:00
This code was originally written by Thomas G. Lane and Todd Newman, and proposed for inclusion in the Independent JPEG Group's software. However, that fell through when Tom left the IJG. It was not written by Marti Maria - Little cms only had a copy of that code. This code has recently been merged into libjpeg-turbo, so it seems reasonable to now consider libjpeg-turbo as the canonical source of these files. However, since the GIMP carries the original version of the code proposed for IJG's JPEG, and doesn't contain any of the more recent and minor tweaks made during its inclusion in libjpeg-turbo, only the original copyright holders (ie., Thomas G. Lane and Todd Newman) are mentioned. The relevant license text was lifted from the Independent JPEG Group's software, similar to the way it's done in Chromium and other users of this code. https://gitlab.gnome.org/GNOME/gimp/issues/2119
245 lines
8.0 KiB
C
245 lines
8.0 KiB
C
/* jpeg-icc.c
|
|
*
|
|
* This file provides code to read and write International Color
|
|
* Consortium (ICC) device profiles embedded in JFIF JPEG image files.
|
|
* The ICC has defined a standard format for including such data in
|
|
* JPEG "APP2" markers. The code given here does not know anything
|
|
* about the internal structure of the ICC profile data; it just knows
|
|
* how to put the profile data into a JPEG file being written, or get
|
|
* it back out when reading.
|
|
*
|
|
* This code depends on new features added to the IJG JPEG library as of
|
|
* IJG release 6b; it will not compile or work with older IJG versions.
|
|
*/
|
|
|
|
/* This code was originally proposed for the Independent JPEG Group's
|
|
* software, but that fell through, and only recently got merged into
|
|
* libjpeg-turbo. This code comes with the following copyright notice:
|
|
*
|
|
* Copyright (C) 1997-1998, Thomas G. Lane, Todd Newman.
|
|
*
|
|
* For conditions of distribution and use, see the accompanying
|
|
* COPYING.ijg file.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <jpeglib.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "jpeg-icc.h"
|
|
|
|
|
|
/*
|
|
* Since an ICC profile can be larger than the maximum size of a JPEG
|
|
* marker (64K), we need provisions to split it into multiple markers.
|
|
* The format defined by the ICC specifies one or more APP2 markers
|
|
* containing the following data:
|
|
*
|
|
* Identifying string ASCII "ICC_PROFILE\0" (12 bytes)
|
|
* Marker sequence number 1 for first APP2, 2 for next, etc (1 byte)
|
|
* Number of markers Total number of APP2's used (1 byte)
|
|
* Profile data (remainder of APP2 data)
|
|
|
|
* Decoders should use the marker sequence numbers to reassemble the
|
|
* profile, rather than assuming that the APP2 markers appear in the
|
|
* correct sequence.
|
|
*/
|
|
|
|
#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */
|
|
#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */
|
|
#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */
|
|
#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)
|
|
|
|
|
|
/*
|
|
* This routine writes the given ICC profile data into a JPEG file.
|
|
* It *must* be called AFTER calling jpeg_start_compress() and BEFORE
|
|
* the first call to jpeg_write_scanlines().
|
|
* (This ordering ensures that the APP2 marker(s) will appear after the
|
|
* SOI and JFIF or Adobe markers, but before all else.)
|
|
*/
|
|
|
|
void
|
|
jpeg_icc_write_profile (j_compress_ptr cinfo,
|
|
const guchar *icc_data_ptr,
|
|
guint icc_data_len)
|
|
{
|
|
unsigned int num_markers; /* total number of markers we'll write */
|
|
int cur_marker = 1; /* per spec, counting starts at 1 */
|
|
unsigned int length; /* number of bytes to write in this marker */
|
|
|
|
/* Calculate the number of markers we'll need, rounding up of course */
|
|
num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER;
|
|
if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len)
|
|
num_markers++;
|
|
|
|
while (icc_data_len > 0) {
|
|
/* length of profile to put in this marker */
|
|
length = icc_data_len;
|
|
if (length > MAX_DATA_BYTES_IN_MARKER)
|
|
length = MAX_DATA_BYTES_IN_MARKER;
|
|
icc_data_len -= length;
|
|
|
|
/* Write the JPEG marker header (APP2 code and marker length) */
|
|
jpeg_write_m_header(cinfo, ICC_MARKER,
|
|
(unsigned int) (length + ICC_OVERHEAD_LEN));
|
|
|
|
/* Write the marker identifying string "ICC_PROFILE" (null-terminated).
|
|
* We code it in this less-than-transparent way so that the code works
|
|
* even if the local character set is not ASCII.
|
|
*/
|
|
jpeg_write_m_byte(cinfo, 0x49);
|
|
jpeg_write_m_byte(cinfo, 0x43);
|
|
jpeg_write_m_byte(cinfo, 0x43);
|
|
jpeg_write_m_byte(cinfo, 0x5F);
|
|
jpeg_write_m_byte(cinfo, 0x50);
|
|
jpeg_write_m_byte(cinfo, 0x52);
|
|
jpeg_write_m_byte(cinfo, 0x4F);
|
|
jpeg_write_m_byte(cinfo, 0x46);
|
|
jpeg_write_m_byte(cinfo, 0x49);
|
|
jpeg_write_m_byte(cinfo, 0x4C);
|
|
jpeg_write_m_byte(cinfo, 0x45);
|
|
jpeg_write_m_byte(cinfo, 0x0);
|
|
|
|
/* Add the sequencing info */
|
|
jpeg_write_m_byte(cinfo, cur_marker);
|
|
jpeg_write_m_byte(cinfo, (int) num_markers);
|
|
|
|
/* Add the profile data */
|
|
while (length--) {
|
|
jpeg_write_m_byte(cinfo, *icc_data_ptr);
|
|
icc_data_ptr++;
|
|
}
|
|
cur_marker++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handy subroutine to test whether a saved marker is an ICC profile marker.
|
|
*/
|
|
|
|
static boolean
|
|
marker_is_icc (jpeg_saved_marker_ptr marker)
|
|
{
|
|
return
|
|
marker->marker == ICC_MARKER &&
|
|
marker->data_length >= ICC_OVERHEAD_LEN &&
|
|
/* verify the identifying string */
|
|
GETJOCTET(marker->data[0]) == 0x49 &&
|
|
GETJOCTET(marker->data[1]) == 0x43 &&
|
|
GETJOCTET(marker->data[2]) == 0x43 &&
|
|
GETJOCTET(marker->data[3]) == 0x5F &&
|
|
GETJOCTET(marker->data[4]) == 0x50 &&
|
|
GETJOCTET(marker->data[5]) == 0x52 &&
|
|
GETJOCTET(marker->data[6]) == 0x4F &&
|
|
GETJOCTET(marker->data[7]) == 0x46 &&
|
|
GETJOCTET(marker->data[8]) == 0x49 &&
|
|
GETJOCTET(marker->data[9]) == 0x4C &&
|
|
GETJOCTET(marker->data[10]) == 0x45 &&
|
|
GETJOCTET(marker->data[11]) == 0x0;
|
|
}
|
|
|
|
|
|
/*
|
|
* See if there was an ICC profile in the JPEG file being read;
|
|
* if so, reassemble and return the profile data.
|
|
*
|
|
* TRUE is returned if an ICC profile was found, FALSE if not.
|
|
* If TRUE is returned, *icc_data_ptr is set to point to the
|
|
* returned data, and *icc_data_len is set to its length.
|
|
*
|
|
* NOTE: if the file contains invalid ICC APP2 markers, we just silently
|
|
* return FALSE. You might want to issue an error message instead.
|
|
*/
|
|
|
|
gboolean
|
|
jpeg_icc_read_profile (j_decompress_ptr cinfo,
|
|
guchar **icc_data_ptr,
|
|
guint *icc_data_len)
|
|
{
|
|
jpeg_saved_marker_ptr marker;
|
|
int num_markers = 0;
|
|
int seq_no;
|
|
JOCTET *icc_data;
|
|
unsigned int total_length;
|
|
#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */
|
|
char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */
|
|
unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */
|
|
unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */
|
|
|
|
*icc_data_ptr = NULL; /* avoid confusion if FALSE return */
|
|
*icc_data_len = 0;
|
|
|
|
/* This first pass over the saved markers discovers whether there are
|
|
* any ICC markers and verifies the consistency of the marker numbering.
|
|
*/
|
|
|
|
for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++)
|
|
marker_present[seq_no] = 0;
|
|
|
|
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
|
if (marker_is_icc(marker)) {
|
|
if (num_markers == 0)
|
|
num_markers = GETJOCTET(marker->data[13]);
|
|
else if (num_markers != GETJOCTET(marker->data[13]))
|
|
return FALSE; /* inconsistent num_markers fields */
|
|
seq_no = GETJOCTET(marker->data[12]);
|
|
if (seq_no <= 0 || seq_no > num_markers)
|
|
return FALSE; /* bogus sequence number */
|
|
if (marker_present[seq_no])
|
|
return FALSE; /* duplicate sequence numbers */
|
|
marker_present[seq_no] = 1;
|
|
data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN;
|
|
}
|
|
}
|
|
|
|
if (num_markers == 0)
|
|
return FALSE;
|
|
|
|
/* Check for missing markers, count total space needed,
|
|
* compute offset of each marker's part of the data.
|
|
*/
|
|
|
|
total_length = 0;
|
|
for (seq_no = 1; seq_no <= num_markers; seq_no++) {
|
|
if (marker_present[seq_no] == 0)
|
|
return FALSE; /* missing sequence number */
|
|
data_offset[seq_no] = total_length;
|
|
total_length += data_length[seq_no];
|
|
}
|
|
|
|
if (total_length <= 0)
|
|
return FALSE; /* found only empty markers? */
|
|
|
|
/* Allocate space for assembled data */
|
|
icc_data = g_try_malloc (total_length * sizeof(JOCTET));
|
|
if (icc_data == NULL)
|
|
return FALSE; /* oops, out of memory */
|
|
|
|
/* and fill it in */
|
|
for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
|
|
if (marker_is_icc(marker)) {
|
|
JOCTET FAR *src_ptr;
|
|
JOCTET *dst_ptr;
|
|
unsigned int length;
|
|
seq_no = GETJOCTET(marker->data[12]);
|
|
dst_ptr = icc_data + data_offset[seq_no];
|
|
src_ptr = marker->data + ICC_OVERHEAD_LEN;
|
|
length = data_length[seq_no];
|
|
while (length--) {
|
|
*dst_ptr++ = *src_ptr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
*icc_data_ptr = icc_data;
|
|
*icc_data_len = total_length;
|
|
|
|
return TRUE;
|
|
}
|