Skip to content

Commit 7b980ab

Browse files
committed
fix: update cropped image when crop is set from server side
Close #19
1 parent 6c920d2 commit 7b980ab

2 files changed

Lines changed: 88 additions & 90 deletions

File tree

src/main/java/com/flowingcode/vaadin/addons/imagecrop/ImageCrop.java

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* #%L
33
* Image Crop Add-on
44
* %%
5-
* Copyright (C) 2024 Flowing Code
5+
* Copyright (C) 2024-2025 Flowing Code
66
* %%
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@
4848
@CssImport("react-image-crop/dist/ReactCrop.css")
4949
@CssImport("./styles/image-crop-styles.css")
5050
public class ImageCrop extends ReactAdapterComponent {
51-
51+
5252
private static final String IMG_FULL_HEIGHT_CLASS_NAME = "img-full-height";
5353

5454
private String croppedImageDataUri;
@@ -59,9 +59,9 @@ public class ImageCrop extends ReactAdapterComponent {
5959
* @param src the URL of the image to be cropped
6060
*/
6161
public ImageCrop(String src) {
62-
this.setImageSrc(src);
63-
this.addCroppedImageListener(this::updateCroppedImage);
64-
this.croppedImageDataUri = src;
62+
setImageSrc(src);
63+
addCroppedImageListener(this::updateCroppedImage);
64+
croppedImageDataUri = src;
6565
}
6666

6767
/**
@@ -71,7 +71,7 @@ public ImageCrop(String src) {
7171
*/
7272
public ImageCrop(Image image) {
7373
this(image.getSrc());
74-
image.getAlt().ifPresent(a -> this.setImageAlt(a));
74+
image.getAlt().ifPresent(a -> setImageAlt(a));
7575
}
7676

7777
/**
@@ -93,7 +93,7 @@ protected Registration addCroppedImageListener(
9393
* @param event the event containing the new cropped image data URI
9494
*/
9595
private void updateCroppedImage(CroppedImageEvent event) {
96-
this.croppedImageDataUri = event.getCroppedImageDataUri();
96+
croppedImageDataUri = event.getCroppedImageDataUri();
9797
}
9898

9999
/**
@@ -134,11 +134,12 @@ public String getImageAlt() {
134134

135135
/**
136136
* Defines the crop dimensions.
137-
*
137+
*
138138
* @param crop the crop dimensions
139139
*/
140140
public void setCrop(Crop crop) {
141141
setState("crop", crop);
142+
getElement().executeJs("this._updateCroppedImage(this.crop)");
142143
}
143144

144145
/**
@@ -151,7 +152,7 @@ public Crop getCrop() {
151152
/**
152153
* Sets the aspect ratio of the crop.
153154
* For example, 1 for a square or 16/9 for landscape.
154-
*
155+
*
155156
* @param aspect the aspect ratio of the crop
156157
*/
157158
public void setAspect(double aspect) {
@@ -171,7 +172,7 @@ public double getAspect() {
171172
* Sets whether the crop area should be shown as a circle.
172173
* If the aspect ratio is not 1, the circle will be warped into an oval shape.
173174
* Defaults to false.
174-
*
175+
*
175176
* @param circularCrop true to show the crop area as a circle, false otherwise
176177
*/
177178
public void setCircularCrop(boolean circularCrop) {
@@ -190,7 +191,7 @@ public boolean isCircularCrop() {
190191
/**
191192
* Sets whether the selection can't be disabled if the user clicks outside
192193
* the selection area. Defaults to false.
193-
*
194+
*
194195
* @param keepSelection true so selection can't be disabled if the user clicks
195196
* outside the selection area, false otherwise.
196197
*/
@@ -209,7 +210,7 @@ public boolean isKeepSelection() {
209210

210211
/**
211212
* Sets whether the user cannot resize or draw a new crop. Defaults to false.
212-
*
213+
*
213214
* @param disabled true to disable crop resizing and drawing, false otherwise
214215
*/
215216
public void setDisabled(boolean disabled) {
@@ -228,7 +229,7 @@ public boolean isDisabled() {
228229
/**
229230
* Sets whether the user cannot create or resize a crop, but can still drag the
230231
* existing crop around. Defaults to false.
231-
*
232+
*
232233
* @param locked true to lock the crop, false otherwise
233234
*/
234235
public void setLocked(boolean locked) {
@@ -246,7 +247,7 @@ public boolean isLocked() {
246247

247248
/**
248249
* Sets a minimum crop width, in pixels.
249-
*
250+
*
250251
* @param minWidth the minimum crop width
251252
*/
252253
public void setCropMinWidth(Integer minWidth) {
@@ -264,7 +265,7 @@ public Integer getCropMinWidth() {
264265

265266
/**
266267
* Sets a minimum crop height, in pixels.
267-
*
268+
*
268269
* @param minHeight the minimum crop height
269270
*/
270271
public void setCropMinHeight(Integer minHeight) {
@@ -282,7 +283,7 @@ public Integer getCropMinHeight() {
282283

283284
/**
284285
* Sets a maximum crop width, in pixels.
285-
*
286+
*
286287
* @param maxWidth the maximum crop width
287288
*/
288289
public void setCropMaxWidth(Integer maxWidth) {
@@ -300,7 +301,7 @@ public Integer getCropMaxWidth() {
300301

301302
/**
302303
* Sets a maximum crop height, in pixels.
303-
*
304+
*
304305
* @param maxHeight the maximum crop height
305306
*/
306307
public void setCropMaxHeight(Integer maxHeight) {
@@ -319,7 +320,7 @@ public Integer getCropMaxHeight() {
319320
/**
320321
* Sets whether to show rule of thirds lines in the cropped area. Defaults to
321322
* false.
322-
*
323+
*
323324
* @param ruleOfThirds true to show rule of thirds lines, false otherwise
324325
*/
325326
public void setRuleOfThirds(boolean ruleOfThirds) {
@@ -341,7 +342,7 @@ public boolean isRuleOfThirds() {
341342
* @return the cropped image data URI
342343
*/
343344
public String getCroppedImageDataUri() {
344-
return this.croppedImageDataUri;
345+
return croppedImageDataUri;
345346
}
346347

347348
/**
@@ -353,26 +354,27 @@ public String getCroppedImageDataUri() {
353354
* @param fullHeight whether the image should fill the viewport height
354355
*/
355356
public void setImageFullHeight(Boolean fullHeight) {
356-
if (fullHeight)
357-
this.addClassName(IMG_FULL_HEIGHT_CLASS_NAME);
358-
else
359-
this.removeClassName(IMG_FULL_HEIGHT_CLASS_NAME);
357+
if (fullHeight) {
358+
addClassName(IMG_FULL_HEIGHT_CLASS_NAME);
359+
} else {
360+
removeClassName(IMG_FULL_HEIGHT_CLASS_NAME);
361+
}
360362
}
361363

362364
/**
363365
* Decodes the cropped image data URI and returns it as a byte array. If the image data URI is not
364366
* in the format "data:image/*;base64,", it will be decoded assuming it is a Base64 encoded
365367
* string.
366-
*
368+
*
367369
* <p>
368370
* This method incorporates work licensed under MIT. Copyright 2021-2023 David "F0rce" Dodlek
369371
* https://github.com/F0rce/cropper
370372
* </p>
371-
*
373+
*
372374
* @return byte[] the decoded byte array of the cropped image
373375
*/
374376
public byte[] getCroppedImageBase64() {
375-
String croppedDataUri = this.getCroppedImageDataUri();
377+
String croppedDataUri = getCroppedImageDataUri();
376378
if (StringUtils.isBlank(croppedDataUri)) {
377379
return null;
378380
}

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

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* #%L
33
* Image Crop Add-on
44
* %%
5-
* Copyright (C) 2024 Flowing Code
5+
* Copyright (C) 2024-2025 Flowing Code
66
* %%
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
2121
import { ReactAdapterElement, RenderHooks } from 'Frontend/generated/flow/ReactAdapter';
2222
import { JSXElementConstructor, ReactElement, useRef, useEffect } from "react";
2323
import React from 'react';
24-
import { type Crop, ReactCrop, PixelCrop, makeAspectCrop, centerCrop } from "react-image-crop";
24+
import { type Crop, ReactCrop, PixelCrop, PercentCrop, makeAspectCrop, centerCrop, convertToPixelCrop } from "react-image-crop";
2525

2626
class ImageCropElement extends ReactAdapterElement {
2727

@@ -70,6 +70,7 @@ class ImageCropElement extends ReactAdapterElement {
7070
height
7171
)
7272
setCrop(newcrop);
73+
this._updateCroppedImage(newcrop);
7374
}
7475
}
7576
};
@@ -122,69 +123,9 @@ class ImageCropElement extends ReactAdapterElement {
122123
};
123124

124125
const onComplete = (c: PixelCrop) => {
125-
croppedImageEncode(c);
126+
this._updateCroppedImage(c);
126127
};
127-
128-
const croppedImageEncode = (completedCrop: PixelCrop) => {
129-
if (completedCrop) {
130-
131-
// get the image element
132-
const image = imgRef.current;
133-
134-
// create a canvas element to draw the cropped image
135-
const canvas = document.createElement("canvas");
136-
137-
// draw the image on the canvas
138-
if (image) {
139-
const ccrop = completedCrop;
140-
const scaleX = image.naturalWidth / image.width;
141-
const scaleY = image.naturalHeight / image.height;
142-
const ctx = canvas.getContext("2d");
143-
const pixelRatio = window.devicePixelRatio;
144-
canvas.width = ccrop.width * pixelRatio;
145-
canvas.height = ccrop.height * pixelRatio;
146-
147-
if (ctx) {
148-
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
149-
ctx.imageSmoothingQuality = "high";
150-
151-
ctx.save();
152-
153-
if (circularCrop) {
154-
canvas.width = ccrop.width;
155-
canvas.height = ccrop.height;
156-
157-
ctx.beginPath();
158-
159-
ctx.arc(ccrop.width / 2, ccrop.height / 2, ccrop.height / 2, 0, Math.PI * 2, true);
160-
ctx.closePath();
161-
ctx.clip();
162-
}
163-
164-
ctx.drawImage(
165-
image,
166-
ccrop.x * scaleX,
167-
ccrop.y * scaleY,
168-
ccrop.width * scaleX,
169-
ccrop.height * scaleY,
170-
0,
171-
0,
172-
ccrop.width,
173-
ccrop.height
174-
);
175-
176-
ctx.restore();
177-
}
178-
179-
// get the cropped image
180-
let croppedImageDataUri = canvas.toDataURL("image/png", 1.0);
181-
182-
// dispatch the event containing cropped image
183-
this.fireCroppedImageEvent(croppedImageDataUri);
184-
}
185-
}
186-
}
187-
128+
188129
return (
189130
<ReactCrop
190131
crop={crop}
@@ -219,6 +160,61 @@ class ImageCropElement extends ReactAdapterElement {
219160
})
220161
);
221162
}
163+
164+
public _updateCroppedImage(crop: PixelCrop|PercentCrop) {
165+
const image = this.querySelector("img");
166+
if (crop && image) {
167+
168+
crop = convertToPixelCrop(crop, image.width, image.height);
169+
170+
// create a canvas element to draw the cropped image
171+
const canvas = document.createElement("canvas");
172+
173+
// draw the image on the canvas
174+
const ccrop = crop;
175+
const scaleX = image.naturalWidth / image.width;
176+
const scaleY = image.naturalHeight / image.height;
177+
const ctx = canvas.getContext("2d");
178+
const pixelRatio = window.devicePixelRatio;
179+
canvas.width = ccrop.width * pixelRatio;
180+
canvas.height = ccrop.height * pixelRatio;
181+
182+
if (ctx) {
183+
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
184+
ctx.imageSmoothingQuality = "high";
185+
ctx.save();
186+
187+
if (this.circularCrop) {
188+
canvas.width = ccrop.width;
189+
canvas.height = ccrop.height;
190+
ctx.beginPath();
191+
ctx.arc(ccrop.width / 2, ccrop.height / 2, ccrop.height / 2, 0, Math.PI * 2, true);
192+
ctx.closePath();
193+
ctx.clip();
194+
}
195+
196+
ctx.drawImage(
197+
image,
198+
ccrop.x * scaleX,
199+
ccrop.y * scaleY,
200+
ccrop.width * scaleX,
201+
ccrop.height * scaleY,
202+
0,
203+
0,
204+
ccrop.width,
205+
ccrop.height
206+
);
207+
208+
ctx.restore();
209+
210+
// get the cropped image
211+
let croppedImageDataUri = canvas.toDataURL("image/png", 1.0);
212+
213+
// dispatch the event containing cropped image
214+
this.fireCroppedImageEvent(croppedImageDataUri);
215+
}
216+
}
217+
}
222218
}
223219

224220
customElements.define("image-crop", ImageCropElement);

0 commit comments

Comments
 (0)