Commit ace9a10d authored by Noel Gordon's avatar Noel Gordon Committed by Commit Bot

[piexwasm] Add embedded adobe color profile to JPEG images

Add createImageDataArray_ helper and use it to convert the view to the
final data: a new Uint8Array(view) as before, but if the image is JPEG
and it has 'adobeRgb' color space, embed an adobe color profile in the
returned JPEG image data.

Remove a TODO: the using code in image_request_task.js no longer needs
to manually color correct the image from adobe to sRGB when drawing to
the <canvas>. The chrome <canvas> is color managed, and uses the color
profile embedded in the image to perform color correction to sRGB.

Bug: 1132695
Change-Id: Iec21fb4021d8ab94a377f9fc682873854041bb87
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2454591Reviewed-by: default avatarAlex Danilo <adanilo@chromium.org>
Commit-Queue: Noel Gordon <noel@chromium.org>
Auto-Submit: Noel Gordon <noel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814552}
parent d64bf40b
......@@ -181,56 +181,3 @@ ImageLoaderUtil.calculateCopyParameters = function(source, request) {
}
};
};
/**
* Matrix converts AdobeRGB color space into sRGB color space.
* @const {!Array<number>}
*/
ImageLoaderUtil.MATRIX_FROM_ADOBE_TO_STANDARD = [
1.39836, -0.39836, 0.00000,
0.00000, 1.00000, 0.00000,
0.00000, -0.04293, 1.04293
];
/**
* Converts the canvas of color space into sRGB. TODO(noel): the Chrome <canvas>
* is color managed today. Is this code still needed?
* @param {HTMLCanvasElement} target Target canvas.
* @param {string} colorSpace Current color space.
*/
ImageLoaderUtil.convertColorSpace = function(target, colorSpace) {
if (colorSpace === 'adobeRgb') {
const matrix = ImageLoaderUtil.MATRIX_FROM_ADOBE_TO_STANDARD;
const context =
assertInstanceof(target.getContext('2d'), CanvasRenderingContext2D);
const imageData = context.getImageData(0, 0, target.width, target.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// Scale to [0, 1].
let adobeR = data[i] / 255;
let adobeG = data[i + 1] / 255;
let adobeB = data[i + 2] / 255;
// Apply adobeRgb inverse gamma to convert to linear color.
adobeR = adobeR <= 0.0556 ? adobeR / 32 : Math.pow(adobeR, 2.2);
adobeG = adobeG <= 0.0556 ? adobeG / 32 : Math.pow(adobeG, 2.2);
adobeB = adobeB <= 0.0556 ? adobeB / 32 : Math.pow(adobeB, 2.2);
// Matrix convert linear adobeRgb color to linear sRgb color.
let sR = matrix[0] * adobeR + matrix[1] * adobeG + matrix[2] * adobeB;
let sG = matrix[3] * adobeR + matrix[4] * adobeG + matrix[5] * adobeB;
let sB = matrix[6] * adobeR + matrix[7] * adobeG + matrix[8] * adobeB;
// Convert linear color to sRgb gamma color.
sR = sR <= 0.0031308 ? 12.92 * sR : 1.055 * Math.pow(sR, 1 / 2.4) - 0.055;
sG = sG <= 0.0031308 ? 12.92 * sG : 1.055 * Math.pow(sG, 1 / 2.4) - 0.055;
sB = sB <= 0.0031308 ? 12.92 * sB : 1.055 * Math.pow(sB, 1 / 2.4) - 0.055;
// Scale to [0, 255].
data[i] = Math.max(0, Math.min(255, sR * 255));
data[i + 1] = Math.max(0, Math.min(255, sG * 255));
data[i + 2] = Math.max(0, Math.min(255, sB * 255));
}
context.putImageData(imageData, 0, 0);
}
};
......@@ -605,7 +605,7 @@ ImageRequestTask.prototype.sendImageData_ = function(width, height, data) {
* @private
*/
ImageRequestTask.prototype.onImageLoad_ = function() {
const imageColorSpace = this.colorSpace_ || 'sRgb';
this.colorSpace_ = this.colorSpace_ || 'sRgb';
// Perform processing if the url is not a data url, or if there are some
// operations requested.
......@@ -613,10 +613,8 @@ ImageRequestTask.prototype.onImageLoad_ = function() {
if (!(this.request_.url.match(/^data/) ||
this.request_.url.match(/^drivefs:/)) ||
ImageLoaderUtil.shouldProcess(
this.image_.width, this.image_.height, this.request_) ||
(imageColorSpace !== 'sRgb')) {
this.image_.width, this.image_.height, this.request_)) {
ImageLoaderUtil.resizeAndCrop(this.image_, this.canvas_, this.request_);
ImageLoaderUtil.convertColorSpace(this.canvas_, imageColorSpace);
imageChanged = true; // The image is now on the <canvas>.
}
......
......@@ -354,7 +354,7 @@ class ImageBuffer {
const view = new Uint8Array(this.source.buffer, offset, length);
return {
thumbnail: new Uint8Array(view).buffer,
thumbnail: this.createImageDataArray_(view, preview).buffer,
mimeType: 'image/jpeg',
ifd: this.details_(result, preview.orientation),
orientation: preview.orientation,
......@@ -394,7 +394,7 @@ class ImageBuffer {
const view = new Uint8Array(this.source.buffer, offset, length);
return {
thumbnail: new Uint8Array(view).buffer,
thumbnail: this.createImageDataArray_(view, thumbnail).buffer,
mimeType: 'image/jpeg',
ifd: this.details_(result, thumbnail.orientation),
orientation: thumbnail.orientation,
......@@ -495,6 +495,30 @@ class ImageBuffer {
};
}
/**
* Converts a |view| of the "preview image" to Uint8Array data. Embeds an
* AdobeRGB1998 ICC Color Profile in that data if the preview is JPEG and
* it has 'adodeRgb' color space.
*
* @private
* @param {!PiexWasmPreviewImageMetadata} preview
* @param {!Uint8Array} view
* return {!Uint8Array}
*/
createImageDataArray_(view, preview) {
const jpeg = view.byteLength > 2 && view[0] === 0xff && view[1] === 0xd8;
if (jpeg && preview.colorSpace === 'adobeRgb') {
const data = new Uint8Array(view.byteLength + adobeProfile.byteLength);
data.set(view.subarray(2), 2 + adobeProfile.byteLength);
data.set(adobeProfile, 2);
data.set([0xff, 0xd8], 0);
return data;
}
return new Uint8Array(view);
}
/**
* Returns the RAW image photographic |details| in a JSON-encoded string.
* Only number and string values are retained, and they are formatted for
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment