15

I have a website where people can upload an PNG image and save it. But before they can save it, i need a check if the image contains transparency. Is there an way to check (i prefer JavaScript) if an image is not 24 bits?

<img id="imageId" src=#" onload="checkRestriction(this,'1')" alt=""/>

var isPng24Bit = false;

function checkRestriction(image, id) {
    if(image.colorDepth != 24) {
      PNGis24Bit = false;
    } else {
      PNGis24Bit = true; 
    }
}
Jelle
  • 758
  • 2
  • 14
  • 36

2 Answers2

14

you can use this canvas technique for this purpose and customize this code as your demand
Make sure you resize your canvas to the same size as the image or else some pixels will be transparent where the image doesn't cover the canvas.

c.width=element.width;
c.height=element.height;

Example code and Demo:

var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
var canvas2=document.getElementById("canvas2");
var ctx2=canvas2.getContext("2d");

$p1=$('#results1');
$p2=$('#results2');

var img1=new Image();
img1.crossOrigin='anonymous'
img1.onload=start1;
img1.src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function start1(){

  canvas1.width=img1.width;
  canvas1.height=img1.height;

  ctx1.drawImage(img1,0,0);

  var imgData=ctx1.getImageData(0,0,canvas1.width,canvas1.height);
  var data=imgData.data;
  var found1='Left canvas does not have transparency';
  for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){found1='Left canvas does have transparency'; 
        break;
        }
  }

  $p1.text(found1);

}


var img2=new Image();
img2.crossOrigin='anonymous'
img2.onload=start2;
img2.src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function start2(){

  canvas2.width=img2.width;
  canvas2.height=img2.height;

  ctx2.drawImage(img2,0,0);

  var imgData=ctx2.getImageData(0,0,canvas2.width,canvas2.height);
  var data=imgData.data;
  var found2='Right canvas does not have transparency';
  for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){found2='Right canvas does have transparency'; 
                      break;
                     }
  }

  $p2.text(found2);

}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<p id=results1>Results:</p>
<p id=results2>Results:</p>
<canvas id="canvas1" width=300 height=300></canvas>
<canvas id="canvas2" width=300 height=300></canvas>
Hassan Saeed
  • 6,326
  • 1
  • 39
  • 37
9

I post this as an alternative approach to check via the PNG file's header directly. This saves memory and doesn't have to iterate through pixels, and the same good performance will be the same regardless of image size.

You can do this by loading the file via HTTPRequest or FileReader as ArrayBuffer, then simply check the file header structure using a DataView.

A PNG file always starts with the IHDR chunk so we only need to check if it's actually a PNG file and then assume the offset for the information telling the depth and type.

The depth field can be of value 1, 2, 4, 8 and 16 (1, 2, 4 being indexed, 8 = 24-bit or 8-bits per channel etc.).

The type field can be 0 (grayscale), 2 (true-color or RGB), 3 (indexed), 4 (grayscale + alpha) and 6 (RGB + alpha).

For details on the PNG file format and the IHDR header, see this link.

loadXHR("//i.imgur.com/zpWwpEM.png", function(result) {
  console.log(result); // result.buffer = original arraybuffer
});

function check(buffer, callback) {
  var view = new DataView(buffer);
  
  // is a PNG?
  if (view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A) {
    // We know format field exists in the IHDR chunk. The chunk exists at 
    // offset 8 +8 bytes (size, name) +8 (depth) & +9 (type)
    var depth = view.getUint8(8 + 8 + 8);
    var type  = view.getUint8(8 + 8 + 9);
    
    callback({
      depth: depth,
      type: ["G", "", "RGB", "Indexed", "GA", "", "RGBA"][type],
      buffer: view.buffer,
      hasAlpha: type === 4 || type === 6  // grayscale + alpha or RGB + alpha
    })
  }
}

function loadXHR(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (xhr.status === 200) check(xhr.response, callback);
    else consle.log("Loading error: " + xhr.statusText);
  };
  xhr.send();
}

Same example but inserting image that is being checked into DOM:

loadXHR("//i.imgur.com/zpWwpEM.png", function(result) {
  console.log(result); // result.buffer = original arraybuffer
  
  var img = new Image();
  img.onload = function() {URL.revokeObjectURL(this.src)};
  img.src = URL.createObjectURL(new Blob([result.buffer]));
  document.body.appendChild(img);
});

function check(buffer, callback) {
  var view = new DataView(buffer);
  
  // is a PNG?
  if (view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A) {
    // We know format field exists in the IHDR chunk. The chunk exists at 
    // offset 8 +8 bytes (size, name) +8 (depth) & +9 (type)
    var depth = view.getUint8(8 + 8 + 8);
    var type  = view.getUint8(8 + 8 + 9);
    
    callback({
      depth: depth,
      type: ["G", "", "RGB", "Indexed", "GA", "", "RGBA"][type],
      buffer: view.buffer,
      hasAlpha: type === 4 || type === 6  // grayscale + alpha or RGB + alpha
    })
  }
}

function loadXHR(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (xhr.status === 200) check(xhr.response, callback);
    else consle.log("Loading error: " + xhr.statusText);
  };
  xhr.send();
}
  • 2
    If you know the exact position of the ihdr header, you could also just request that part of the file if the server allows range requests, or by calling Blob.slice(startPos, endPos) on the fileReader.readAsArrayBuffer in case of user supplied file. (I wasn't able to figure out how to read it fom png specs and knew that you do know ;-) ) – Kaiido Dec 23 '16 at 14:24
  • @Kaiido it's a good idea, especially if the file is of some size and from a server. The size you would need would be 26 bytes if the file is compliant (ie. IHDR always first). This would actually also include width and height (offset 0 and 4 in the IHDR chunk as 32-bit values in big-endian) so those are possible to check as well without loading everything :) For FileReader the whole file would of course be loaded into memory first. I would just attach a view to the arraybuffer in that case, but it would be fast as the browser doesn't need to decode any image data before checking. –  Dec 23 '16 at 14:50
  • For the fileReader one, I think memory impact is almost negligible for still images, but for say videos, it's often a good idea to work on the given (unprocessed) Blob and slice it directly before passing it to the fileReader. The Blob is just a pointer to the direct file, so it actually uses no memory. – Kaiido Dec 23 '16 at 15:05
  • @Kaiido yup, agreed - in the case of the File object (as you say, technically a Blob) this is a good approach and should be even faster. –  Dec 23 '16 at 15:14
  • @epistemex works perfect! very well done. You think something like that would be possible for an .svg image as well? – Snowball Aug 05 '18 at 15:20
  • 1
    @Snowball SVG is harder since there is no specific header at an absolute position. But you can parse as XML (or manually) and look for required SVG attributes such as ` –  Aug 07 '18 at 11:53
  • What about webp? This only works for png... – Bruno Marotta Oct 28 '21 at 12:26
  • This always returns true, no matter the format of the image, and it's lack of transparency. I am working with blobs, turning blob to arrayBuffer and then checking using the method above, it's always true for transparency and format (png) – Hooman Askari Mar 31 '23 at 18:36