-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(web): image editor - panel and cropping #11074
Merged
Merged
Changes from 42 commits
Commits
Show all changes
61 commits
Select commit
Hold shift + click to select a range
6154c9c
cropping, panel
ilyaChuk e23fdaf
fix presets
ilyaChuk 100f83d
types
ilyaChuk 31790b2
Merge branch 'immich-app:main' into main
ilyaChuk 539d271
prettier
ilyaChuk 1cb58b9
fix lint
ilyaChuk aa21e08
Merge branch 'immich-app:main' into main
ilyaChuk 0a8bab6
fix aspect ratio, performance optimization
ilyaChuk c81bda4
improved tool selection, removed placeholder
ilyaChuk 71ea55f
fix the mouse's exit from canvas
ilyaChuk 74bac38
fix error
ilyaChuk 21731e8
the "save" button and change tracking
ilyaChuk dcfd221
lint, format
ilyaChuk e498055
the mini functionality of the save button
ilyaChuk c5cff01
fix aspect ratio
ilyaChuk 0f01afe
hide editor button on mobiles
ilyaChuk 019ed53
strict equality
ilyaChuk 1eb7f1c
Use the dollar sign syntax for stores inside components
ilyaChuk 9de3298
unobtrusive grid lines, circles at the corners
ilyaChuk b9e2f6a
more correct image load, handleError
ilyaChuk f982a54
more strict equality
ilyaChuk 7cba82e
fix styles. unused and tailwind
ilyaChuk d17c0da
dont store isShowEditor
ilyaChuk 840d55f
if showEditor - hide navbar & shortcuts
ilyaChuk 574da14
crop-canvas decomposition (danger)
ilyaChuk 7d0e3c8
Merge branch 'main' into main
ilyaChuk 25f8d2d
fix lint
ilyaChuk 1f1b590
Merge branch 'main' into main
ilyaChuk 0790090
fix ts
ilyaChuk 82e6577
Merge branch 'main' into main
ilyaChuk ef1ff62
callback function as props
ilyaChuk bfe85a3
correctly disabling shortcuts
ilyaChuk 2bd17ba
convenient canvas borders
ilyaChuk 4bc6beb
-the editor button for video files, -save button
ilyaChuk 66b29f3
Merge branch 'main' into main
ilyaChuk 22e83ee
hide editor btn if panoramic || gif || live
ilyaChuk 9815252
corners instead of circles (preview), fix lint&format
ilyaChuk a8dd073
confirm close editor without save
ilyaChuk cadd413
vertical aspect ratios
ilyaChuk ba97426
Merge remote-tracking branch 'upstream/main'
ilyaChuk f3d4d76
recovery after merge. editor's closing shortcut
ilyaChuk 5eef4b3
fix format
ilyaChuk 8f6cc65
Merge branch 'main' into main
ilyaChuk 2f1da40
move from canvas to html elements
ilyaChuk 5028a96
fix changes detections
ilyaChuk 6678521
rotation
ilyaChuk 1ed0647
hide detail panel if showing editor
ilyaChuk 20cd0b8
fix aspect ratios near min size
ilyaChuk bc6e18c
Merge branch 'main' into main
ilyaChuk 73ab707
fix crop area when changing image size when rotate
ilyaChuk 219fff9
fix of fix
ilyaChuk dbb3fe4
better layout - grouping
ilyaChuk ae0c5a3
Merge branch 'main' into main
ilyaChuk 53e57ea
Merge branch 'main' into main
ilyaChuk 69043f8
Merge branch 'main' into main
ilyaChuk 59c015f
Merge branch 'main' into main
ilyaChuk bfd6cbe
hide the button
alextran1502 cb1e541
Merge branch 'main' into main
ilyaChuk 63bfc43
fix i18n, format
ilyaChuk c154d00
hide button
ilyaChuk 857f02a
hide button v2
ilyaChuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
web/src/lib/components/asset-viewer/editor/crop-tool/canvas-drawing.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import type { CropSettings } from '$lib/stores/asset-editor.store'; | ||
import { get } from 'svelte/store'; | ||
import { context2D, darkenLevel, imgElement, isResizingOrDragging, padding } from './crop-store'; | ||
const mPadding = get(padding); | ||
export function draw(canvas: HTMLCanvasElement | null, crop: CropSettings) { | ||
const ctx = get(context2D); | ||
const img = get(imgElement); | ||
if (!ctx || !canvas || !img) { | ||
return; | ||
} | ||
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height); | ||
ctx.drawImage(img, mPadding, mPadding, canvas.width - 2 * mPadding, canvas.height - 2 * mPadding); | ||
drawOverlay(canvas, crop); | ||
drawCropRect(crop); | ||
} | ||
|
||
export function drawCropRect(crop: CropSettings) { | ||
const ctx = get(context2D); | ||
if (!ctx) { | ||
return; | ||
} | ||
|
||
ctx.globalCompositeOperation = 'exclusion'; | ||
ctx.strokeStyle = 'white'; | ||
ctx.lineWidth = 2; | ||
ctx.strokeRect(crop.x + mPadding, crop.y + mPadding, crop.width, crop.height); | ||
|
||
if (get(isResizingOrDragging)) { | ||
ctx.strokeStyle = 'white'; | ||
ctx.lineWidth = 1; | ||
|
||
const thirdWidth = crop.width / 3; | ||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + thirdWidth + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + thirdWidth + mPadding, crop.y + crop.height + mPadding); | ||
ctx.stroke(); | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + 2 * thirdWidth + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + 2 * thirdWidth + mPadding, crop.y + crop.height + mPadding); | ||
ctx.stroke(); | ||
|
||
const thirdHeight = crop.height / 3; | ||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + mPadding, crop.y + thirdHeight + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding, crop.y + thirdHeight + mPadding); | ||
ctx.stroke(); | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + mPadding, crop.y + 2 * thirdHeight + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding, crop.y + 2 * thirdHeight + mPadding); | ||
ctx.stroke(); | ||
} | ||
|
||
ctx.globalCompositeOperation = 'source-over'; | ||
//dynamic corners size | ||
const minSide = Math.min(crop.height, crop.width); | ||
const length = minSide > 18 * 5 ? 18 : Math.min(18, minSide / 5); | ||
|
||
ctx.strokeStyle = 'white'; | ||
ctx.lineWidth = mPadding * 2; | ||
ctx.lineJoin = 'round'; | ||
ctx.lineCap = 'round'; | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + mPadding + length, crop.y + mPadding); | ||
ctx.moveTo(crop.x + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + mPadding, crop.y + mPadding + length); | ||
ctx.stroke(); | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + crop.width + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding - length, crop.y + mPadding); | ||
ctx.moveTo(crop.x + crop.width + mPadding, crop.y + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding, crop.y + mPadding + length); | ||
ctx.stroke(); | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + mPadding, crop.y + crop.height + mPadding); | ||
ctx.lineTo(crop.x + mPadding + length, crop.y + crop.height + mPadding); | ||
ctx.moveTo(crop.x + mPadding, crop.y + crop.height + mPadding); | ||
ctx.lineTo(crop.x + mPadding, crop.y + crop.height + mPadding - length); | ||
ctx.stroke(); | ||
|
||
ctx.beginPath(); | ||
ctx.moveTo(crop.x + crop.width + mPadding, crop.y + crop.height + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding - length, crop.y + crop.height + mPadding); | ||
ctx.moveTo(crop.x + crop.width + mPadding, crop.y + crop.height + mPadding); | ||
ctx.lineTo(crop.x + crop.width + mPadding, crop.y + crop.height + mPadding - length); | ||
ctx.stroke(); | ||
} | ||
|
||
export function drawOverlay(canvas: HTMLCanvasElement, crop: CropSettings) { | ||
const ctx = get(context2D); | ||
const darken = get(darkenLevel); | ||
if (!ctx) { | ||
return; | ||
} | ||
|
||
ctx.fillStyle = `rgba(0, 0, 0, ${darken})`; | ||
|
||
ctx.fillRect(mPadding, mPadding, canvas.width - 2 * mPadding, crop.y); | ||
ctx.fillRect(mPadding, crop.y + mPadding, crop.x, crop.height); | ||
ctx.fillRect( | ||
crop.x + crop.width + mPadding, | ||
crop.y + mPadding, | ||
canvas.width - crop.x - crop.width - 2 * mPadding, | ||
crop.height, | ||
); | ||
ctx.fillRect( | ||
mPadding, | ||
crop.y + crop.height + mPadding, | ||
canvas.width - 2 * mPadding, | ||
canvas.height - crop.y - crop.height - 2 * mPadding, | ||
); | ||
} |
76 changes: 76 additions & 0 deletions
76
web/src/lib/components/asset-viewer/editor/crop-tool/crop-canvas.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<script lang="ts"> | ||
import { onMount, afterUpdate, onDestroy } from 'svelte'; | ||
import { t } from 'svelte-i18n'; | ||
import { getAssetOriginalUrl } from '$lib/utils'; | ||
import { handleError } from '$lib/utils/handle-error'; | ||
|
||
import { imgElement, canvasElement, resetCropStore } from './crop-store'; | ||
|
||
import { draw } from './canvas-drawing'; | ||
import { onImageLoad, resizeCanvas } from './image-loading'; | ||
import { handleMouseDown, handleMouseMove, handleMouseUp, handleMouseOut } from './mouse-handlers'; | ||
import { recalculateCrop, animateCropChange } from './crop-settings'; | ||
import { cropAspectRatio, cropSettings, resetGlobalCropStore } from '$lib/stores/asset-editor.store'; | ||
|
||
export let asset; | ||
|
||
let canvas: HTMLCanvasElement; | ||
let img: HTMLImageElement; | ||
|
||
$: imgElement.set(img); | ||
$: canvasElement.set(canvas); | ||
|
||
cropAspectRatio.subscribe((value) => { | ||
if (!$imgElement || !$canvasElement) { | ||
return; | ||
} | ||
const newCrop = recalculateCrop($cropSettings, $canvasElement, value, true); | ||
if (newCrop) { | ||
animateCropChange($cropSettings, newCrop, () => draw($canvasElement, $cropSettings)); | ||
} | ||
}); | ||
|
||
onMount(() => { | ||
resetCropStore(); | ||
resetGlobalCropStore(); | ||
$imgElement = new Image(); | ||
$imgElement.src = getAssetOriginalUrl({ id: asset.id, checksum: asset.checksum }); | ||
$imgElement.addEventListener('load', onImageLoad); | ||
$imgElement.addEventListener('error', (error) => { | ||
handleError(error, $t('error_loading_image')); | ||
}); | ||
|
||
window.addEventListener('mousemove', handleMouseMove); | ||
}); | ||
|
||
onDestroy(() => { | ||
window.removeEventListener('mousemove', handleMouseMove); | ||
resetCropStore(); | ||
resetGlobalCropStore(); | ||
}); | ||
|
||
afterUpdate(() => { | ||
resizeCanvas(); | ||
}); | ||
</script> | ||
|
||
<div class="canvas-container"> | ||
<canvas bind:this={canvas} on:mousedown={handleMouseDown} on:mouseup={handleMouseUp} on:blur={handleMouseOut} | ||
></canvas> | ||
</div> | ||
|
||
<style> | ||
.canvas-container { | ||
width: calc(100% - 4rem); | ||
margin: auto; | ||
margin-top: 2rem; | ||
height: calc(100% - 4rem); | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
canvas { | ||
cursor: default; | ||
} | ||
</style> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm starting to think that using regular HTML elements for the controls might be a better choice. I can think of a couple advantages:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to make a rotation tool yesterday. I had to repeat the course of linear algebra to remember how to work with matrices and vectors 💀.
Yes, the canvas creates difficulties. After yesterday, I also think about switching to html elements