Refactor image metadata handling for user color embedding
- Updated useUserColor hook to support multiple image formats (PNG, WebP, JPEG, GIF) for color embedding and extraction. - Introduced unified image metadata utilities in imageMetadata.ts for detecting formats and handling color operations. - Added GIF metadata utilities in gifMetadata.ts for reading and writing user color. - Implemented JPEG metadata utilities in jpegMetadata.ts for embedding and extracting user color. - Created WebP metadata utilities in webpMetadata.ts for handling user color in WebP images. - Enhanced error handling and logging for unsupported formats and operations.
This commit is contained in:
215
src/app/utils/imageMetadata.ts
Normal file
215
src/app/utils/imageMetadata.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Unified image metadata utilities for embedding/extracting user color
|
||||
* Supports PNG, WebP, JPEG, and GIF formats
|
||||
*/
|
||||
|
||||
/* eslint-disable no-plusplus */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { embedColorInPNG, extractColorFromPNG, removeColorFromPNG } from './pngMetadata';
|
||||
import { embedColorInWebP, extractColorFromWebP, removeColorFromWebP } from './webpMetadata';
|
||||
import { embedColorInJPEG, extractColorFromJPEG, removeColorFromJPEG } from './jpegMetadata';
|
||||
import { embedColorInGIF, extractColorFromGIF, removeColorFromGIF } from './gifMetadata';
|
||||
|
||||
/** PNG signature bytes */
|
||||
const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
||||
|
||||
/** RIFF/WebP signature bytes */
|
||||
const RIFF_SIGNATURE = [0x52, 0x49, 0x46, 0x46]; // "RIFF"
|
||||
const WEBP_OFFSET_SIGNATURE = [0x57, 0x45, 0x42, 0x50]; // "WEBP" at offset 8
|
||||
|
||||
/** JPEG signature */
|
||||
const JPEG_SIGNATURE = [0xFF, 0xD8];
|
||||
|
||||
/** GIF signatures */
|
||||
const GIF87A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61]; // "GIF87a"
|
||||
const GIF89A_SIGNATURE = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]; // "GIF89a"
|
||||
|
||||
export type ImageFormat = 'png' | 'webp' | 'jpeg' | 'gif' | 'unknown';
|
||||
|
||||
/**
|
||||
* Detect image format from raw data
|
||||
*/
|
||||
export function detectImageFormat(data: ArrayBuffer | Uint8Array): ImageFormat {
|
||||
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
||||
|
||||
if (bytes.length < 12) return 'unknown';
|
||||
|
||||
// Check PNG signature
|
||||
let isPNG = true;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
if (bytes[i] !== PNG_SIGNATURE[i]) {
|
||||
isPNG = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isPNG) return 'png';
|
||||
|
||||
// Check WebP signature (RIFF + WEBP)
|
||||
let isRIFF = true;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (bytes[i] !== RIFF_SIGNATURE[i]) {
|
||||
isRIFF = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isRIFF) {
|
||||
let isWebP = true;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (bytes[8 + i] !== WEBP_OFFSET_SIGNATURE[i]) {
|
||||
isWebP = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isWebP) return 'webp';
|
||||
}
|
||||
|
||||
// Check JPEG signature (FFD8)
|
||||
if (bytes[0] === JPEG_SIGNATURE[0] && bytes[1] === JPEG_SIGNATURE[1]) {
|
||||
return 'jpeg';
|
||||
}
|
||||
|
||||
// Check GIF signatures
|
||||
let isGIF87a = true;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (bytes[i] !== GIF87A_SIGNATURE[i]) {
|
||||
isGIF87a = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isGIF87a) return 'gif';
|
||||
|
||||
let isGIF89a = true;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (bytes[i] !== GIF89A_SIGNATURE[i]) {
|
||||
isGIF89a = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isGIF89a) return 'gif';
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract paarrot color from image data (PNG, WebP, JPEG, or GIF)
|
||||
* @param imageData - Raw image data as ArrayBuffer or Uint8Array
|
||||
* @returns The color string if found, or undefined
|
||||
*/
|
||||
export function extractColorFromImage(imageData: ArrayBuffer | Uint8Array): string | undefined {
|
||||
const format = detectImageFormat(imageData);
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return extractColorFromPNG(imageData);
|
||||
case 'webp':
|
||||
return extractColorFromWebP(imageData);
|
||||
case 'jpeg':
|
||||
return extractColorFromJPEG(imageData);
|
||||
case 'gif':
|
||||
return extractColorFromGIF(imageData);
|
||||
default:
|
||||
console.warn('[extractColorFromImage] Unknown image format');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed paarrot color into image data (PNG, WebP, JPEG, or GIF)
|
||||
* @param imageData - Raw image data as ArrayBuffer or Uint8Array
|
||||
* @param color - The color string to embed (e.g., '#ff5500')
|
||||
* @returns New image data with embedded color, or null if not a supported format
|
||||
*/
|
||||
export function embedColorInImage(imageData: ArrayBuffer | Uint8Array, color: string): Uint8Array | null {
|
||||
const format = detectImageFormat(imageData);
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return embedColorInPNG(imageData, color);
|
||||
case 'webp':
|
||||
return embedColorInWebP(imageData, color);
|
||||
case 'jpeg':
|
||||
return embedColorInJPEG(imageData, color);
|
||||
case 'gif':
|
||||
return embedColorInGIF(imageData, color);
|
||||
default:
|
||||
console.warn('[embedColorInImage] Unknown image format, cannot embed color');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove paarrot color from image data (PNG, WebP, JPEG, or GIF)
|
||||
* @param imageData - Raw image data as ArrayBuffer or Uint8Array
|
||||
* @returns New image data without the color, or null if not a supported format
|
||||
*/
|
||||
export function removeColorFromImage(imageData: ArrayBuffer | Uint8Array): Uint8Array | null {
|
||||
const format = detectImageFormat(imageData);
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return removeColorFromPNG(imageData);
|
||||
case 'webp':
|
||||
return removeColorFromWebP(imageData);
|
||||
case 'jpeg':
|
||||
return removeColorFromJPEG(imageData);
|
||||
case 'gif':
|
||||
return removeColorFromGIF(imageData);
|
||||
default:
|
||||
console.warn('[removeColorFromImage] Unknown image format');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for an image format
|
||||
*/
|
||||
export function getMimeType(format: ImageFormat): string {
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return 'image/png';
|
||||
case 'webp':
|
||||
return 'image/webp';
|
||||
case 'jpeg':
|
||||
return 'image/jpeg';
|
||||
case 'gif':
|
||||
return 'image/gif';
|
||||
default:
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file extension for an image format
|
||||
*/
|
||||
export function getExtension(format: ImageFormat): string {
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return 'png';
|
||||
case 'webp':
|
||||
return 'webp';
|
||||
case 'jpeg':
|
||||
return 'jpg';
|
||||
case 'gif':
|
||||
return 'gif';
|
||||
default:
|
||||
return 'bin';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and extract color from an image URL (supports PNG and WebP)
|
||||
* @param url - HTTP URL to fetch the image from
|
||||
* @returns The color string if found, or undefined
|
||||
*/
|
||||
export async function fetchAndExtractColor(url: string): Promise<string | undefined> {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) return undefined;
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
return extractColorFromImage(data);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user