chromium/chrome/browser/resources/new_tab_page/transparency.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Utility which gives a best effort guess on whether a supplied image file's
 * bytes represent an image with transparency.
 */
export function checkTransparency(buffer: ArrayBuffer): boolean {
  const view = new DataView(buffer);

  return isTransparentPNG(view) || isTransparentBMP(view) ||
      isTransparentWebP(view);
}

/**
 * Safely gets Uint8 value from DataView.
 *
 * Swallows potential RangeErrors, opting to return null in those cases.
 */
function getUint8FromView(view: DataView, offset: number): number|null {
  try {
    return view.getUint8(offset);
  } catch {
    return null;
  }
}

/**
 * Safely gets Uint16 value from DataView.
 *
 * Swallows potential RangeErrors, opting to return null in those cases.
 */
function getUint16FromView(view: DataView, offset: number): number|null {
  try {
    return view.getUint16(offset);
  } catch {
    return null;
  }
}

/**
 * Safely gets Uint32 value from DataView.
 *
 * Swallows potential RangeErrors, opting to return null in those cases.
 */
function getUint32FromView(view: DataView, offset: number): number|null {
  try {
    return view.getUint32(offset);
  } catch {
    return null;
  }
}

/**
 * Whether a DataView represents a PNG image.
 */
export function isPNG(view: DataView): boolean {
  // 89 50 4E 47 is PNG magic number.
  // Next four bytes should always be 0D 0A 1A 0A.
  return getUint32FromView(view, 0) === 0x89504E47 &&
      getUint32FromView(view, 4) === 0x0D0A1A0A;
}

function isTransparentPNG(view: DataView): boolean {
  if (!isPNG(view)) {
    return false;
  }
  // We know format field exists in the IHDR chunk. The chunk exists at
  // offset 8 +8 bytes (size, name) +8 (depth) & +9 (type) = 25 byte offset.
  const type = getUint8FromView(view, 25);
  return type === 0x04 || type === 0x06;  // grayscale + alpha or RGB + alpha
}

/**
 * Whether a DataView represents a WebP image.
 */
export function isWebP(view: DataView): boolean {
  // 52 49 46 46 || <ignore 4 bytes> || 57 45 42 50 is the WebP magic number.
  // R  I  F  F  || <ignore 4 bytes> || W  E  B  P in ASCII.
  // https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
  return getUint32FromView(view, 0) === 0x52494646 &&
      getUint32FromView(view, 8) === 0x57454250;
}

/**
 * Whether a DataView represents a WebP image.
 * Checks WebP format (VP8 | VP8L | VP8X). VP8 never has an alpha channel. We
 * make an assumption that VP8X and VP8L always have alpha channels. That is
 * not true, but does simplify the logic and for practical purposes, this is
 * good enough of an assumption for us.
 */
function isTransparentWebP(view: DataView): boolean {
  if (!isWebP(view)) {
    return false;
  }
  // Fourteenth byte indicates WebP format. There are three variations of WebP
  // encoding: VP8, VP8L, and VP8X.
  //
  // Offset: 12 13 14 15
  // ASCII:  V  P  8  ? <- ? == " "  || "L"  || "X"
  // Hex:    56 50 38 ? <- ? == 0x20 || 0x4C || 0x58
  const format = getUint8FromView(view, 15);
  // 58 indicates VP8X, 4C indicates VP8L.
  return format === 0x58 || format === 0x4C;
}

/**
 * Whether a DataView represents a BMP image.
 */
export function isBMP(view: DataView): boolean {
  // 42 4D is the BMP magic number.
  return getUint16FromView(view, 0) === 0x424D;
}

function isTransparentBMP(view: DataView): boolean {
  if (!isBMP(view)) {
    return false;
  }
  // Check the value of the bit count field, 2 bytes, 28 byte offset.
  return getUint16FromView(view, 28) === 0x32;
}