// lib/enhance/types.ts
interface ImageAnalysis {
brightness: {
mean: number;
histogram: number[]; // 256 buckets
suggested: number; // adjustment
};
contrast: {
range: number;
stdDev: number;
suggested: number; // multiplier
};
saturation: {
mean: number;
isGrayscale: boolean;
suggested: number; // adjustment
};
sharpness: {
score: number; // 0-100
suggested: number; // amount
};
}
interface EnhancementParams {
brightness: number; // -50 to +50
contrast: number; // 0.5 to 2.0
saturation: number; // -50 to +50
sharpness: number; // 0 to 100
}
// lib/enhance/analyze.ts
function analyzeImage(
imageData: ImageData
): ImageAnalysis {
const pixels = imageData.data;
const pixelCount = pixels.length / 4;
let totalBrightness = 0;
let minBrightness = 255;
let maxBrightness = 0;
const histogram = new Array(256)
.fill(0);
let totalSaturation = 0;
let satValues: number[] = [];
for (let i = 0;
i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
// Brightness (luminance)
const bright = Math.round(
0.299 * r + 0.587 * g
+ 0.114 * b);
totalBrightness += bright;
minBrightness =
Math.min(minBrightness, bright);
maxBrightness =
Math.max(maxBrightness, bright);
histogram[bright]++;
// Saturation (from HSL)
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const l = (max + min) / 2 / 255;
const sat = max === min ? 0 :
l < 0.5
? (max - min) / (max + min)
: (max - min)
/ (510 - max - min);
totalSaturation += sat;
satValues.push(sat);
}
const meanBright =
totalBrightness / pixelCount;
const contrastRange =
maxBrightness - minBrightness;
const meanSat =
totalSaturation / pixelCount;
// Saturation variance → detect B&W
const satVariance = variance(
satValues);
const isGrayscale =
satVariance < 0.01;
return {
brightness: {
mean: meanBright,
histogram,
suggested:
suggestBrightness(meanBright),
},
contrast: {
range: contrastRange,
stdDev: stdDev(histogram),
suggested:
suggestContrast(contrastRange),
},
saturation: {
mean: meanSat,
isGrayscale,
suggested: isGrayscale ? 0 :
suggestSaturation(meanSat),
},
sharpness: {
score: estimateSharpness(
imageData),
suggested: 0, // MVP: skip
},
};
}
// lib/enhance/suggestions.ts
function suggestBrightness(
mean: number
): number {
const target = 128;
const diff = target - mean;
// Gentle correction: 30% of diff
// Clamped to safe range
return clamp(
Math.round(diff * 0.3),
-40, 40
);
}
function suggestContrast(
range: number
): number {
if (range < 80)
return 1.4; // very flat → boost
if (range < 150)
return 1.2; // somewhat flat
if (range > 240)
return 0.9; // too punchy → reduce
return 1.0; // good range
}
function suggestSaturation(
mean: number
): number {
if (mean < 0.15)
return 20; // washed out → boost
if (mean < 0.25)
return 10; // slightly dull
if (mean > 0.7)
return -15; // oversaturated
return 0; // looks good
}
function clamp(
val: number, min: number, max: number
) {
return Math.min(max,
Math.max(min, val));
}
// __tests__/enhance.test.ts
import { describe, it, expect } from
'vitest';
function createTestImage(
r: number, g: number, b: number,
size = 100
): ImageData {
const data =
new Uint8ClampedArray(size * size * 4);
for (let i = 0;
i < data.length; i += 4) {
data[i] = r; data[i+1] = g;
data[i+2] = b; data[i+3] = 255;
}
return new ImageData(data, size, size);
}
describe('analyzeImage', () => {
it('detects dark image', () => {
const img = createTestImage(
30, 30, 30);
const result = analyzeImage(img);
expect(result.brightness.mean)
.toBeCloseTo(30);
expect(result.brightness.suggested)
.toBeGreaterThan(0);
});
it('detects bright image', () => {
const img = createTestImage(
230, 230, 230);
const result = analyzeImage(img);
expect(result.brightness.suggested)
.toBeLessThan(0);
});
it('detects grayscale', () => {
const img = createTestImage(
128, 128, 128);
const result = analyzeImage(img);
expect(result.saturation.isGrayscale)
.toBe(true);
expect(result.saturation.suggested)
.toBe(0);
});
it('suggests no change for good image',
() => {
const img = createTestImage(
128, 100, 80);
const result = analyzeImage(img);
expect(Math.abs(
result.brightness.suggested))
.toBeLessThan(10);
});
});
// Record enhancement for analytics
app.post('/api/enhance/analytics',
requireAuth,
async (req, res) => {
const { imageId, analysis,
userAdjustments, applied } =
req.body;
await db.collection('enhancements')
.insertOne({
imageId,
userId: req.user.id,
analysis,
applied: applied
? userAdjustments : null,
userAdjusted:
applied && !deepEqual(
analysis.suggested,
userAdjustments),
timestamp: new Date(),
});
res.json({ recorded: true });
});
git switch -c feature/auto-enhance-engine
git add src/lib/enhance/ src/__tests__/
git commit -m "Add image analysis engine + unit tests"
git push origin feature/auto-enhance-engine
# PR → Review → Merge ✅
Image histograms reveal everything about a photo's exposure.
A histogram is a frequency distribution of pixel brightness values (0-255). Professional photographers check histograms to judge exposure quality.