Skip to content

Commit 615e37a

Browse files
committed
fix: use image's MIME type instead of default image/png
Close #23
1 parent a226022 commit 615e37a

File tree

1 file changed

+52
-5
lines changed

1 file changed

+52
-5
lines changed

src/main/resources/META-INF/resources/frontend/src/image-crop.tsx

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class ImageCropElement extends ReactAdapterElement {
7070
height
7171
)
7272
setCrop(newcrop);
73-
this._updateCroppedImage(newcrop);
73+
this._updateCroppedImage(newcrop).catch(console.error);
7474
}
7575
}
7676
};
@@ -123,7 +123,7 @@ class ImageCropElement extends ReactAdapterElement {
123123
};
124124

125125
const onComplete = (c: PixelCrop) => {
126-
this._updateCroppedImage(c);
126+
this._updateCroppedImage(c).catch(console.error);
127127
};
128128

129129
return (
@@ -160,11 +160,56 @@ class ImageCropElement extends ReactAdapterElement {
160160
})
161161
);
162162
}
163+
164+
/**
165+
* Attempts to detect the MIME type of given HTMLImageElement.
166+
*
167+
* Resolution order:
168+
* 1. If the image is a data URL, extracts the MIME type directly.
169+
* 2. Otherwise, sends a HEAD request to the image URL to read
170+
* the Content-Type header (faster, no body download).
171+
* 3. If the HEAD request does not provide Content-Type, falls back
172+
* to fetching the full image as a Blob and using `blob.type`.
173+
*
174+
* @param img The HTMLImageElement whose MIME type should be detected.
175+
* @returns A Promise resolving to the MIME type string (e.g. "image/png"),
176+
* or null if it cannot be determined.
177+
*/
178+
async #getImageMimeType(img: HTMLImageElement): Promise<string | null> {
179+
if (!img.src) {
180+
return null;
181+
}
182+
183+
// Case 1: data URL (e.g., data:image/png;base64,...)
184+
if (img.src.startsWith("data:")) {
185+
const semiIndex = img.src.indexOf(";");
186+
if (semiIndex > 5) {
187+
return img.src.substring(5, semiIndex);
188+
}
189+
return null;
190+
}
191+
192+
try {
193+
// Case 2: try a HEAD request (fast, no body)
194+
const headRes = await fetch(img.src, { method: "HEAD" });
195+
const mime = headRes.headers.get("Content-Type");
196+
if (mime) {
197+
return mime;
198+
}
199+
200+
// Case 3: fallback — fetch full blob
201+
const blobRes = await fetch(img.src);
202+
const blob = await blobRes.blob();
203+
return blob.type || null;
204+
} catch (err) {
205+
console.error("Error fetching image MIME type:", err);
206+
return null;
207+
}
208+
}
163209

164-
public _updateCroppedImage(crop: PixelCrop|PercentCrop) {
210+
public async _updateCroppedImage(crop: PixelCrop|PercentCrop) {
165211
const image = this.querySelector("img");
166212
if (crop && image) {
167-
168213
crop = convertToPixelCrop(crop, image.width, image.height);
169214

170215
// create a canvas element to draw the cropped image
@@ -207,8 +252,10 @@ class ImageCropElement extends ReactAdapterElement {
207252

208253
ctx.restore();
209254

255+
const imgMimeType = await this.#getImageMimeType(image) || 'image/png';
256+
210257
// get the cropped image
211-
let croppedImageDataUri = canvas.toDataURL("image/png", 1.0);
258+
let croppedImageDataUri = canvas.toDataURL(imgMimeType, 1.0);
212259

213260
// dispatch the event containing cropped image
214261
this.fireCroppedImageEvent(croppedImageDataUri);

0 commit comments

Comments
 (0)