Lewati ke konten utama

Pemrosesan Gambar

Proses gambar di sisi klien sebelum mengupload untuk mengurangi bandwidth dan biaya penyimpanan.

Kompresi Gambar

import { useDropup } from '@samithahansaka/dropup';
import { compressImage } from '@samithahansaka/dropup/image';

function CompressedUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
accept: 'image/*',
upload: { url: '/api/upload' },

// Proses file sebelum upload
onFilesAdded: async (newFiles) => {
for (const file of newFiles) {
// Kompres gambar
const compressed = await compressImage(file.file, {
maxWidth: 1920,
maxHeight: 1080,
quality: 0.8,
});

// Ganti dengan versi terkompresi
actions.updateFileMeta(file.id, {
originalSize: file.size,
compressedFile: compressed,
});
}
},
});

return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Gambar akan dikompres sebelum upload</p>
</div>
);
}

Opsi Kompresi

interface CompressOptions {
// Dimensi maksimum (mempertahankan rasio aspek)
maxWidth?: number; // default: 1920
maxHeight?: number; // default: 1080

// Kualitas (0-1)
quality?: number; // default: 0.8

// Format output
type?: 'image/jpeg' | 'image/png' | 'image/webp'; // default: tipe asli
}

// Contoh
await compressImage(file, { quality: 0.6 }); // Kualitas lebih rendah
await compressImage(file, { maxWidth: 800, maxHeight: 600 }); // Ukuran lebih kecil
await compressImage(file, { type: 'image/webp' }); // Konversi ke WebP

Preview dengan Kompresi

import { useDropup } from '@samithahansaka/dropup';
import { compressImage } from '@samithahansaka/dropup/image';
import { useState } from 'react';

function PreviewWithCompression() {
const [compressionStats, setCompressionStats] = useState<Map<string, {
original: number;
compressed: number;
}>>(new Map());

const { files, actions, getDropProps, getInputProps } = useDropup({
accept: 'image/*',

onFilesAdded: async (newFiles) => {
for (const file of newFiles) {
const compressed = await compressImage(file.file, {
maxWidth: 1200,
quality: 0.75,
});

setCompressionStats(prev => new Map(prev).set(file.id, {
original: file.size,
compressed: compressed.size,
}));

// Simpan file terkompresi untuk upload
actions.updateFileMeta(file.id, { compressedFile: compressed });
}
},
});

const formatSize = (bytes: number) => {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
};

return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>Lepas gambar untuk dikompres</p>
</div>

<div style={styles.grid}>
{files.map(file => {
const stats = compressionStats.get(file.id);
const savings = stats
? ((1 - stats.compressed / stats.original) * 100).toFixed(0)
: 0;

return (
<div key={file.id} style={styles.card}>
{file.preview && (
<img src={file.preview} alt="" style={styles.preview} />
)}
<p>{file.name}</p>
{stats && (
<p style={styles.stats}>
{formatSize(stats.original)}{formatSize(stats.compressed)}
<span style={styles.savings}> (-{savings}%)</span>
</p>
)}
</div>
);
})}
</div>
</div>
);
}

const styles = {
dropzone: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 40,
textAlign: 'center' as const,
marginBottom: 20,
},
grid: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: 16,
},
card: {
border: '1px solid #eee',
borderRadius: 8,
padding: 12,
},
preview: {
width: '100%',
height: 150,
objectFit: 'cover' as const,
borderRadius: 4,
},
stats: {
fontSize: 12,
color: '#666',
},
savings: {
color: '#4caf50',
fontWeight: 'bold',
},
};

Resize Gambar

import { resizeImage } from '@samithahansaka/dropup/image';

// Resize ke dimensi tepat
const resized = await resizeImage(file, {
width: 300,
height: 300,
mode: 'cover', // 'cover' | 'contain' | 'fill'
});

// Buat thumbnail
const thumbnail = await resizeImage(file, {
width: 150,
height: 150,
mode: 'cover',
});

Mode Resize

ModeDeskripsi
coverMengisi seluruh area, mungkin terpotong
containPas dalam area, mungkin ada ruang kosong
fillMeregangkan untuk mengisi (mungkin terdistorsi)

Perbaikan Orientasi EXIF

Beberapa kamera menyimpan gambar dengan data rotasi EXIF. Perbaiki orientasi sebelum ditampilkan:

import { fixOrientation } from '@samithahansaka/dropup/image';

const corrected = await fixOrientation(file);

Crop Gambar

import { cropImage } from '@samithahansaka/dropup/image';

const cropped = await cropImage(file, {
x: 100, // Mulai X
y: 50, // Mulai Y
width: 400, // Lebar crop
height: 400, // Tinggi crop
});

Komponen Editor Gambar

import { useDropup } from '@samithahansaka/dropup';
import { compressImage, cropImage } from '@samithahansaka/dropup/image';
import { useState, useRef } from 'react';

function ImageEditor() {
const [selectedFile, setSelectedFile] = useState<DropupFile | null>(null);
const [cropArea, setCropArea] = useState({ x: 0, y: 0, width: 200, height: 200 });

const { files, actions, getDropProps, getInputProps } = useDropup({
accept: 'image/*',
maxFiles: 1,
multiple: false,
});

const handleCrop = async () => {
if (!selectedFile) return;

const cropped = await cropImage(selectedFile.file, cropArea);

// Ganti asli dengan versi yang di-crop
actions.updateFileMeta(selectedFile.id, {
processedFile: cropped,
});
};

const handleCompress = async () => {
if (!selectedFile) return;

const compressed = await compressImage(selectedFile.file, {
quality: 0.7,
});

actions.updateFileMeta(selectedFile.id, {
processedFile: compressed,
});
};

return (
<div style={styles.container}>
{files.length === 0 ? (
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>Lepas gambar untuk diedit</p>
</div>
) : (
<div style={styles.editor}>
<div style={styles.preview}>
<img
src={files[0].preview}
alt=""
style={styles.image}
onClick={() => setSelectedFile(files[0])}
/>
</div>

<div style={styles.tools}>
<h4>Alat</h4>
<button onClick={handleCrop}>Crop</button>
<button onClick={handleCompress}>Kompres</button>
<button onClick={() => actions.reset()}>Hapus</button>
</div>
</div>
)}
</div>
);
}

const styles = {
container: {
maxWidth: 600,
margin: '0 auto',
},
dropzone: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 60,
textAlign: 'center' as const,
},
editor: {
display: 'flex',
gap: 20,
},
preview: {
flex: 1,
},
image: {
maxWidth: '100%',
borderRadius: 8,
},
tools: {
width: 150,
display: 'flex',
flexDirection: 'column' as const,
gap: 8,
},
};

Konversi Format Gambar

import { convertImage } from '@samithahansaka/dropup/image';

// Konversi ke WebP untuk ukuran file lebih kecil
const webp = await convertImage(file, 'image/webp');

// Konversi ke JPEG
const jpeg = await convertImage(file, 'image/jpeg', { quality: 0.9 });

// Konversi ke PNG (lossless)
const png = await convertImage(file, 'image/png');

Dapatkan Metadata Gambar

import { getImageMetadata } from '@samithahansaka/dropup/image';

const metadata = await getImageMetadata(file);
console.log(metadata);
// {
// width: 1920,
// height: 1080,
// aspectRatio: 1.78,
// orientation: 1, // Orientasi EXIF
// hasAlpha: false,
// format: 'image/jpeg',
// }

Pemrosesan Pipeline

Rangkai beberapa operasi:

import {
compressImage,
fixOrientation,
resizeImage,
} from '@samithahansaka/dropup/image';

async function processImage(file: File): Promise<File> {
let processed = file;

// Langkah 1: Perbaiki orientasi
processed = await fixOrientation(processed);

// Langkah 2: Resize jika terlalu besar
const metadata = await getImageMetadata(processed);
if (metadata.width > 2000 || metadata.height > 2000) {
processed = await resizeImage(processed, {
maxWidth: 2000,
maxHeight: 2000,
});
}

// Langkah 3: Kompres
processed = await compressImage(processed, {
quality: 0.8,
type: 'image/webp',
});

return processed;
}

// Gunakan di uploader
const { files } = useDropup({
accept: 'image/*',
onFilesAdded: async (newFiles) => {
for (const file of newFiles) {
const processed = await processImage(file.file);
actions.updateFileMeta(file.id, { processedFile: processed });
}
},
});

Dukungan Browser

Pemrosesan gambar menggunakan Canvas API dan didukung di semua browser modern:

FiturChromeFirefoxSafariEdge
Resize/Crop
JPEG/PNG
WebP14+
Perbaikan EXIF