2

can the JavaScript check the color mode of a image?

I did a lot of search about this but the only thing I saw was color mode convert (but the convert wants you to set the original color mode)


I add this: --allow-file-access-from-files to have a full control of img in canvas beacause I am using GoogleChrome

Html

<canvas id="canvas" width=6000 height=7919></canvas>

Js

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var img = new Image();
img.crossOrigin = "anonymous";
img.onload = start;
img.src = "file:///D:/vincent-van-gogh-the-starry-night-picture-157544-2.png";
HudsonPH
  • 1,838
  • 2
  • 22
  • 38
  • There are no APIs for that; canvas APIs deal with CSS (RGB) color space methods (including HSL representation), and of course display hardware is RGB. Exactly what kinds of images are you working with? – Pointy Jun 23 '16 at 12:55
  • You didn't mention the method you're using to obtain the images. If you are sourcing your own images and aren't allowing third-party customization, then you can ensure a specific color mode somehow in your content pipeline (like telling your artists/photographers to give you images in a specific format), and then just always assume that same mode. – Merlyn Morgan-Graham Jun 23 '16 at 12:55
  • And if you load an image into a canvas and then extract the pixels, you get RGB no matter what the image format was originally, if the browser can understand it at all. – Pointy Jun 23 '16 at 12:56
  • If you're allowing customized image content, then you could create a process for submitting the image that does any color mode transformations you need on the server side, and continue to assume a specific color mode on the client side. – Merlyn Morgan-Graham Jun 23 '16 at 12:57
  • @MerlynMorgan-Graham I add some code – HudsonPH Jun 23 '16 at 12:57
  • Looks like you're providing the image. You could just open it up in photoshop/whatever and set the preferred color mode yourself. – Merlyn Morgan-Graham Jun 23 '16 at 12:59
  • @Pointy Only RGb but you can convert the img for cmyk, hsl... – HudsonPH Jun 23 '16 at 12:59
  • @MerlynMorgan-Graham I dont want check in one third app, I want get the color mode by Javascript :/ – HudsonPH Jun 23 '16 at 13:01
  • @HudsonPH Totally understand. Pointy said there's no way to do it client side. I don't know if this is true or not, but it might be the reality you're stuck with. On the plus side, your client will load faster if they don't have to do this conversion on every single image every time they load up your app. – Merlyn Morgan-Graham Jun 23 '16 at 13:03
  • @HudsonPH you can map from one color space to another, sure, but that's not what your question appears to be about. You asked about *checking* the color mode of an image. You'd write code to interpret the raw image data, figure out its format (if you don't know it), and then determine what the format says about the color representation. – Pointy Jun 23 '16 at 13:06
  • @HudsonPH and `.png` files are either gray scale or trichromatic (RGB). – Pointy Jun 23 '16 at 13:08
  • @Pointy I made simple app in js to convert a color of a img, works fine but I want have the full control of the information, and I want to know too what the js can do for me – HudsonPH Jun 23 '16 at 13:10
  • @Pointy and if is possible to know how can I do this? – HudsonPH Jun 23 '16 at 13:12
  • You would have to write a lot of software. It would not be a small project. If you could explain exactly why you think you need to do that (especially since you're dealing with RGB color in the browser anyway), it might help people understand the issue. Interpreting image encoding formats is a large topic. The Wikipedia article on PNG format, for example, is quite long. – Pointy Jun 23 '16 at 13:14
  • @Pointy textile printer only print CMYK so I need to know the color mode. I can do this and the back-end but I want see if is possible in the front-end – HudsonPH Jun 23 '16 at 13:19
  • @Pointy and the js can solve the png just using the alpha (not big deal) – HudsonPH Jun 23 '16 at 13:22

3 Answers3

5

Yes - basically JavaScript is able to determine the color mode of a png, but therefore it would be required to 1. convert png to base64 2. convert base64 to byte array 3. reading / parsing the array regarding png specification

A possible approach could look like this:

var PNG = {

        parse: function(imgTag) {
            var base64 = PNG.asBase64(imgTag);
            var byteData = PNG.utils.base64StringToByteArray(base64);
            var parsedPngData = PNG.utils.parseBytes(byteData);

            return PNG.utils.enrichParsedData(parsedPngData);
        },

        asBase64: function(imgTag) {
            var canvas = document.createElement("canvas");
            canvas.width = imgTag.width; 
            canvas.height = imgTag.height; 
            var ctx = canvas.getContext("2d"); 
            ctx.drawImage(imgTag, 0, 0); 
            var dataURL = canvas.toDataURL("image/png");
            return dataURL.split('base64,')[1];
        },

        utils: {
            base64StringToByteArray: function(base64String) {
                //http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
                var byteCharacters = atob(base64String);
                var byteNumbers = new Array(byteCharacters.length);
                for (var i = 0; i < byteCharacters.length; i++) {
                    byteNumbers[i] = byteCharacters.charCodeAt(i);
                }
                return new Uint8Array(byteNumbers);
            },

            parseBytes: function(bytes) {
                var pngData = {};
                //see https://en.wikipedia.org/wiki/Portable_Network_Graphics

                //verify file header
                pngData['headerIsValid'] = bytes[0] == 0x89
                            && bytes[1] == 0x50
                            && bytes[2] == 0x4E
                            && bytes[3] == 0x47
                            && bytes[4] == 0x0D
                            && bytes[5] == 0x0A
                            && bytes[6] == 0x1A
                            && bytes[7] == 0x0A

                if (!pngData.headerIsValid) {
                    console.warn('Provided data does not belong to a png');
                    return pngData;
                }

                //parsing chunks
                var chunks = [];

                var chunk = PNG.utils.parseChunk(bytes, 8);
                chunks.push(chunk);

                while (chunk.name !== 'IEND') {
                    chunk = PNG.utils.parseChunk(bytes, chunk.end);
                    chunks.push(chunk);
                }

                pngData['chunks'] = chunks;
                return pngData;
            },

            parseChunk: function(bytes, start) {
                var chunkLength = PNG.utils.bytes2Int(bytes.slice(start, start + 4));

                var chunkName = '';
                chunkName += String.fromCharCode(bytes[start + 4]);
                chunkName += String.fromCharCode(bytes[start + 5]);
                chunkName += String.fromCharCode(bytes[start + 6]);
                chunkName += String.fromCharCode(bytes[start + 7]);

                var chunkData = [];
                for (var idx = start + 8; idx<chunkLength + start + 8; idx++) {
                    chunkData.push(bytes[idx]);
                }

                //TODO validate crc as required!

                return {
                    start: start,
                    end: Number(start) + Number(chunkLength) + 12, //12 = 4 (length) + 4 (name) + 4 (crc)
                    length: chunkLength,
                    name: chunkName,
                    data: chunkData,
                    crc: [
                        bytes[chunkLength + start + 8],
                        bytes[chunkLength + start + 9],
                        bytes[chunkLength + start + 10],
                        bytes[chunkLength + start + 11]
                    ],
                    crcChecked: false
                };
            },

            enrichParsedData: function(pngData) {
                var idhrChunk = PNG.utils.getChunk(pngData, 'IDHR');

                //see http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html
                pngData.width = PNG.utils.bytes2Int(idhrChunk.data.slice(0, 4));
                pngData.height = PNG.utils.bytes2Int(idhrChunk.data.slice(4, 8));
                pngData.bitDepth = PNG.utils.bytes2Int(idhrChunk.data.slice(8, 9));
                pngData.colorType = PNG.utils.bytes2Int(idhrChunk.data.slice(9, 10));
                pngData.compressionMethod = PNG.utils.bytes2Int(idhrChunk.data.slice(10, 11));
                pngData.filterMethod = PNG.utils.bytes2Int(idhrChunk.data.slice(11, 12));
                pngData.interlaceMethod = PNG.utils.bytes2Int(idhrChunk.data.slice(12, 13));

                pngData.isGreyScale = pngData.colorType == 0 || pngData.colorType == 4;
                pngData.isRgb = pngData.colorType == 2 || pngData.colorType == 6;
                pngData.hasAlpha = pngData.colorType == 4 || pngData.colorType == 6;
                pngData.hasPaletteMode = pngData.colorType == 3 && PNG.utils.getChunk(pngData, 'PLTE') != null;

                return pngData;
            },

            getChunks: function(pngData, chunkName) {
                var chunksForName = [];
                for (var idx = 0; idx<pngData.chunks.length; idx++) {
                    if (pngData.chunks[idx].name = chunkName) {
                        chunksForName.push(pngData.chunks[idx]);
                    }
                }
                return chunksForName;
            },

            getChunk: function(pngData, chunkName) {
                for (var idx = 0; idx<pngData.chunks.length; idx++) {
                    if (pngData.chunks[idx].name = chunkName) {
                        return pngData.chunks[idx];
                    }
                }
                return null;
            },

            bytes2Int: function(bytes) {
                var ret = 0;

                for (var idx = 0; idx<bytes.length; idx++) {
                    ret += bytes[idx];
                    if (idx < bytes.length - 1) {
                        ret = ret << 8;
                    }
                }

                return ret;
            }
        }
    }

Which could be used as follows:

var pngData = PNG.parse(document.getElementById('yourImageId'));
console.log(pngData);

It contains some information, like color mode, number of chunks, the chunks itself, bit depth, etc.

Hope it helps.

Martin Ackermann
  • 884
  • 6
  • 15
  • Above code works for png but does not work for jpeg images.how to get parseBytes for jpeg images?" ReferenceError: pngData is not defined at eval (eval at parse (file:///D:/poc/color.html:13:33), :1:1) at Object.parse (file:///D:/poc/color.html:19:33) at clicked (file:///D:/poc/color.html:160:7) at HTMLButtonElement.onclick (file:///D:/poc/color.html:183:30)" – Robert Ravikumar Dec 12 '18 at 02:35
  • Yes, above code is specifically designed for PNG parsing. If you need equivalent implementation for jpeg images, you would need to implement it following the jpeg format specification. In this case, only the methods "PNG.asBase64(imgTag);" and "PNG.utils.base64StringToByteArray(base64);" are useful for you. – Martin Ackermann Dec 13 '18 at 12:16
  • @MartinAckermann what about parsing a PNG file in order to check the color of each pixel? I would like to do this in Javascript, unfortunately ChunkyPNG is not available in Javascript...the Ruby code I want to follow is: image = ChunkyPNG::Image.from_file(file). .....then .... if image[col, row] == ChunkyPNG::Color::BLACK mask[row, col] = false else mask[row, col] = true end – preston Aug 07 '19 at 09:40
  • Currently I have used FileReader API to get the file from the img tag so I have something like File {name: "maze_text.png", lastModified: 1565158981565, lastModifiedDate: Wed Aug 07 2019 14:23:01 GMT+0800, webkitRelativePath: "", size: 490, …} – preston Aug 07 '19 at 09:42
  • I can also get the file just staright from the the image tag's source and I get a data url. e.g. data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAlCAYAAAAA7LqSAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gsYEgMhfnutHQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABTklEQVRYw91Y2wrDMAjVkP//ZfcwWkq3eNeUBgYr5OLRc4wGAYDgBWMCABD9YkHE5SLL/Iy9pYGIMOAlY1g94/FYG7WqxhU0Ip7fRLSkF0c7zp.............etc. – preston Aug 07 '19 at 09:44
3

Can the JavaScript check the color mode of a image?

Yes and no.

No if you use ordinary image loading. All images that are supported by the browser are converted to RGB(A) before they are handed to us through the Image object (HTMLImageElement).

At this point no information about the original format and color model is presented except from its size and origin.

Canvas only deals with RGBA as all other elements that are displayed and there exist no native method to address color models such as CMYK.

You could always read the RGBA values and convert to CMYK naively, however, without a ICC profile for the target printer you would run into all sort of problems due to the wider gamut of RGB, and printer as well as print medium characteristics which must be adjusted for through ICC profiles. Meaning the result will not produce correct colors in most cases.

You would have to have a server-side solution setup for this to work. Send image to server, convert to CMYK/apply ICC, then send to printer. ImageMagick could be one way to go about it. The browser is simply not cut (nor intended) for print oriented processing.

Yes if you're willing to write image parser manually as well as ICC parser. You would need to support a variety of formats and format combinations, and also be able to apply ICC/gamma on import as well as export to CMYK through the target ICC (meaning you need to support XYZ/LAB color spaces).

The export would require you to write a file writer supporting the CMYK color space and ICC embedding.

The maturing process, bugs, instabilities etc. would be a natural part of the process and likely not something you would want to use in production right away.

If you're just after the color mode of the file and can load the image like ordinary you can of course load the file first through XHR, then scan the file using typed array to locate the information describing the color format depending on format. Then pass the typed array as Blob -> object-URL to load as an image, go through canvas and convert each pixel to CMYK values.

But from this point you will have the same problems as described above and would not escape writing the file writer etc.

1

You can use exifreader package to read image metadata.

import ExifReader from 'exifreader'

const reader = new FileReader()

reader.onload = (event) => {
  const metadata = ExifReader.load(event.target.result, { expanded: true })
  const { file: { 'Color Components': { value } } } = metadata

  switch (value) {
  case 3: console.log('RGB'); break
  case 4: console.log('CMYK'); break
  default: console.log('Unknown')
  }
}

reader.readAsArrayBuffer(instaceOfFileObject)

It doesn't have to be File object, but the ExifReader.load() expects array buffer as its first arg.

Reference: https://www.get-metadata.com/file-info/color-components

elquimista
  • 2,181
  • 2
  • 23
  • 32