update
This commit is contained in:
@ -6,6 +6,8 @@ export interface CropperOptions {
|
||||
shape: ProfileShape;
|
||||
aspectRatio: number;
|
||||
minSize?: number;
|
||||
outputSize?: number;
|
||||
outputQuality?: number;
|
||||
}
|
||||
|
||||
export class ImageCropper {
|
||||
@ -37,6 +39,8 @@ export class ImageCropper {
|
||||
constructor(options: CropperOptions) {
|
||||
this.options = {
|
||||
minSize: 50,
|
||||
outputSize: 800, // Higher default resolution
|
||||
outputQuality: 0.95, // Higher quality
|
||||
...options
|
||||
};
|
||||
|
||||
@ -96,8 +100,8 @@ export class ImageCropper {
|
||||
container.appendChild(this.canvas);
|
||||
container.appendChild(this.overlayCanvas);
|
||||
|
||||
// Calculate image scale and position
|
||||
const scale = Math.max(
|
||||
// Calculate image scale to fit within container (not fill)
|
||||
const scale = Math.min(
|
||||
containerSize / this.img.width,
|
||||
containerSize / this.img.height
|
||||
);
|
||||
@ -107,7 +111,12 @@ export class ImageCropper {
|
||||
this.imageOffsetY = (containerSize - this.img.height * scale) / 2;
|
||||
|
||||
// Initialize crop area
|
||||
this.cropSize = containerSize * 0.8;
|
||||
// Make the crop area fit within the actual image bounds
|
||||
const scaledImageWidth = this.img.width * scale;
|
||||
const scaledImageHeight = this.img.height * scale;
|
||||
const maxCropSize = Math.min(scaledImageWidth, scaledImageHeight, containerSize * 0.8);
|
||||
|
||||
this.cropSize = maxCropSize * 0.8; // Start at 80% of max possible size
|
||||
this.cropX = (containerSize - this.cropSize) / 2;
|
||||
this.cropY = (containerSize - this.cropSize) / 2;
|
||||
}
|
||||
@ -162,8 +171,14 @@ export class ImageCropper {
|
||||
const dx = x - this.dragStartX;
|
||||
const dy = y - this.dragStartY;
|
||||
|
||||
this.cropX = Math.max(0, Math.min(this.canvas.width - this.cropSize, this.cropX + dx));
|
||||
this.cropY = Math.max(0, Math.min(this.canvas.height - this.cropSize, this.cropY + dy));
|
||||
// Constrain crop area to image bounds
|
||||
const minX = this.imageOffsetX;
|
||||
const maxX = this.imageOffsetX + this.img.width * this.imageScale - this.cropSize;
|
||||
const minY = this.imageOffsetY;
|
||||
const maxY = this.imageOffsetY + this.img.height * this.imageScale - this.cropSize;
|
||||
|
||||
this.cropX = Math.max(minX, Math.min(maxX, this.cropX + dx));
|
||||
this.cropY = Math.max(minY, Math.min(maxY, this.cropY + dy));
|
||||
|
||||
this.dragStartX = x;
|
||||
this.dragStartY = y;
|
||||
@ -247,36 +262,52 @@ export class ImageCropper {
|
||||
const dx = x - this.dragStartX;
|
||||
const dy = y - this.dragStartY;
|
||||
|
||||
// Get image bounds
|
||||
const imgLeft = this.imageOffsetX;
|
||||
const imgTop = this.imageOffsetY;
|
||||
const imgRight = this.imageOffsetX + this.img.width * this.imageScale;
|
||||
const imgBottom = this.imageOffsetY + this.img.height * this.imageScale;
|
||||
|
||||
switch (this.resizeHandle) {
|
||||
case 'se':
|
||||
this.cropSize = Math.max(this.minCropSize, Math.min(
|
||||
this.cropSize + Math.max(dx, dy),
|
||||
Math.min(
|
||||
this.canvas.width - this.cropX,
|
||||
this.canvas.height - this.cropY
|
||||
imgRight - this.cropX,
|
||||
imgBottom - this.cropY
|
||||
)
|
||||
));
|
||||
break;
|
||||
case 'nw':
|
||||
const newSize = Math.max(this.minCropSize, this.cropSize - Math.max(dx, dy));
|
||||
const sizeDiff = this.cropSize - newSize;
|
||||
this.cropX += sizeDiff;
|
||||
this.cropY += sizeDiff;
|
||||
this.cropSize = newSize;
|
||||
const newX = this.cropX + sizeDiff;
|
||||
const newY = this.cropY + sizeDiff;
|
||||
if (newX >= imgLeft && newY >= imgTop) {
|
||||
this.cropX = newX;
|
||||
this.cropY = newY;
|
||||
this.cropSize = newSize;
|
||||
}
|
||||
break;
|
||||
case 'ne':
|
||||
const neSizeDx = Math.max(dx, -dy);
|
||||
const neNewSize = Math.max(this.minCropSize, this.cropSize + neSizeDx);
|
||||
const neSizeDiff = neNewSize - this.cropSize;
|
||||
this.cropY -= neSizeDiff;
|
||||
this.cropSize = neNewSize;
|
||||
const neNewY = this.cropY - neSizeDiff;
|
||||
if (neNewY >= imgTop && this.cropX + neNewSize <= imgRight) {
|
||||
this.cropY = neNewY;
|
||||
this.cropSize = neNewSize;
|
||||
}
|
||||
break;
|
||||
case 'sw':
|
||||
const swSizeDx = Math.max(-dx, dy);
|
||||
const swNewSize = Math.max(this.minCropSize, this.cropSize + swSizeDx);
|
||||
const swSizeDiff = swNewSize - this.cropSize;
|
||||
this.cropX -= swSizeDiff;
|
||||
this.cropSize = swNewSize;
|
||||
const swNewX = this.cropX - swSizeDiff;
|
||||
if (swNewX >= imgLeft && this.cropY + swNewSize <= imgBottom) {
|
||||
this.cropX = swNewX;
|
||||
this.cropSize = swNewSize;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -286,6 +317,10 @@ export class ImageCropper {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.overlayCtx.clearRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
|
||||
|
||||
// Fill background
|
||||
this.ctx.fillStyle = '#000000';
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Draw image
|
||||
this.ctx.drawImage(
|
||||
this.img,
|
||||
@ -295,9 +330,14 @@ export class ImageCropper {
|
||||
this.img.height * this.imageScale
|
||||
);
|
||||
|
||||
// Draw overlay
|
||||
// Draw overlay only over the image area
|
||||
this.overlayCtx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||
this.overlayCtx.fillRect(0, 0, this.overlayCanvas.width, this.overlayCanvas.height);
|
||||
this.overlayCtx.fillRect(
|
||||
this.imageOffsetX,
|
||||
this.imageOffsetY,
|
||||
this.img.width * this.imageScale,
|
||||
this.img.height * this.imageScale
|
||||
);
|
||||
|
||||
// Clear crop area
|
||||
this.overlayCtx.save();
|
||||
@ -365,13 +405,21 @@ export class ImageCropper {
|
||||
const cropCanvas = document.createElement('canvas');
|
||||
const cropCtx = cropCanvas.getContext('2d')!;
|
||||
|
||||
// Set output size
|
||||
const outputSize = 400;
|
||||
// Calculate the actual crop size in original image pixels
|
||||
const scale = 1 / this.imageScale;
|
||||
const originalCropSize = this.cropSize * scale;
|
||||
|
||||
// Use requested output size, but warn if upscaling
|
||||
const outputSize = this.options.outputSize!;
|
||||
|
||||
if (outputSize > originalCropSize) {
|
||||
console.info(`Profile picture: Upscaling from ${Math.round(originalCropSize)}px to ${outputSize}px`);
|
||||
}
|
||||
|
||||
cropCanvas.width = outputSize;
|
||||
cropCanvas.height = outputSize;
|
||||
|
||||
// Calculate source coordinates
|
||||
const scale = 1 / this.imageScale;
|
||||
const sx = (this.cropX - this.imageOffsetX) * scale;
|
||||
const sy = (this.cropY - this.imageOffsetY) * scale;
|
||||
const sSize = this.cropSize * scale;
|
||||
@ -383,6 +431,10 @@ export class ImageCropper {
|
||||
cropCtx.clip();
|
||||
}
|
||||
|
||||
// Enable image smoothing for quality
|
||||
cropCtx.imageSmoothingEnabled = true;
|
||||
cropCtx.imageSmoothingQuality = 'high';
|
||||
|
||||
// Draw cropped image
|
||||
cropCtx.drawImage(
|
||||
this.img,
|
||||
@ -390,7 +442,11 @@ export class ImageCropper {
|
||||
0, 0, outputSize, outputSize
|
||||
);
|
||||
|
||||
return cropCanvas.toDataURL('image/jpeg', 0.9);
|
||||
// Detect format from original image
|
||||
const isPng = this.options.image.includes('image/png');
|
||||
const format = isPng ? 'image/png' : 'image/jpeg';
|
||||
|
||||
return cropCanvas.toDataURL(format, this.options.outputQuality);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
|
Reference in New Issue
Block a user