UNPKG

photo-editor

Version:
714 lines (706 loc) 21.6 kB
import { Tool } from "../chunk-W32WS4AD.js"; // src/tools/Blur.ts import { unwrap } from "krustykrab"; var Pixel = class _Pixel { r; g; b; constructor(r, g, b) { this.r = r; this.g = g; this.b = b; } add(pixel) { return new _Pixel(this.r + pixel.r, this.g + pixel.g, this.b + pixel.b); } sub(pixel) { return new _Pixel(this.r - pixel.r, this.g - pixel.g, this.b - pixel.b); } mul(value) { return new _Pixel(this.r * value, this.g * value, this.b * value); } div(value) { return new _Pixel(this.r / value, this.g / value, this.b / value); } clone() { return new _Pixel(this.r, this.g, this.b); } }; function boxesForGauss(sigma, n) { const wIdeal = Math.sqrt(12 * sigma * sigma / n + 1); let wl = Math.floor(wIdeal); if (wl % 2 === 0) { --wl; } const wu = wl + 2; const mIdeal = (12 * sigma * sigma - n * wl * wl - 4 * n * wl - 3 * n) / (-4 * wl - 4); const m = Math.round(mIdeal); const sizes = []; for (let i = 0; i < n; ++i) { sizes.push(i < m ? wl : wu); } return sizes; } function boxBlurH(scl, tcl, w, h, r) { const iarr = 1 / (r + r + 1); for (let i = 0; i < h; ++i) { let ti = i * w; let li = ti; let ri = ti + r; const fv = scl[ti]; const lv = scl[ti + (w - 1)]; let val = fv.mul(r + 1); for (let j = 0; j < r; ++j) { val = val.add(scl[ti + j]); } for (let j = 0; j <= r; ++j) { val = val.add(scl[ri].sub(fv)); ++ri; tcl[ti] = val.mul(iarr); ++ti; } for (let j = r + 1; j < w - r; ++j) { val = val.add(scl[ri++].sub(scl[li])); ++li; tcl[ti] = val.mul(iarr); ++ti; } for (let j = w - r; j < w; ++j) { val = val.add(lv.sub(scl[li])); ++li; tcl[ti] = val.mul(iarr); ++ti; } } } function boxBlurT(scl, tcl, w, h, r) { const iarr = 1 / (r + r + 1); for (let i = 0; i < w; ++i) { let ti = i; let li = ti; let ri = ti + r * w; const fv = scl[ti]; const lv = scl[ti + w * (h - 1)]; let val = fv.mul(r + 1); for (let j = 0; j < r; ++j) { val = val.add(scl[ti + j * w]); } for (let j = 0; j <= r; j++) { val = val.add(scl[ri].sub(fv)); tcl[ti] = val.mul(iarr); ri += w; ti += w; } for (let j = r + 1; j < h - r; j++) { val = val.add(scl[ri].sub(scl[li])); tcl[ti] = val.mul(iarr); li += w; ri += w; ti += w; } for (let j = h - r; j < h; j++) { val = val.add(lv.sub(scl[li])); tcl[ti] = val.mul(iarr); li += w; ti += w; } } } function boxBlur(scl, tcl, w, h, r) { for (let i = 0; i < scl.length; ++i) { tcl[i] = scl[i].clone(); } boxBlurH(tcl, scl, w, h, r); boxBlurT(scl, tcl, w, h, r); } function gaussBlur(scl, tcl, w, h, r) { const bxs = boxesForGauss(r, 3); boxBlur(scl, tcl, w, h, (bxs[0] - 1) / 2); boxBlur(tcl, scl, w, h, (bxs[1] - 1) / 2); boxBlur(scl, tcl, w, h, (bxs[2] - 1) / 2); } var Blur = class extends Tool { bluring = false; lastX = null; lastY = null; sigma = 3; radius = 10; setRadius(value) { this.radius = Math.max(value, 1); } getRadius() { return this.radius; } setSigma(value) { this.sigma = Math.max(value, 0.1); } getSigma() { return this.sigma; } onStartDraw = (event) => { const canvas = unwrap(this.el); this.bluring = true; const newLastX = event.offsetX / (canvas.clientWidth / canvas.width); const newLastY = event.offsetY / (canvas.clientHeight / canvas.height); this.blurAtPoint(newLastX, newLastY); this.lastX = newLastX; this.lastY = newLastY; }; onProcessDraw = (event) => { if (!this.bluring) { return; } const canvas = unwrap(this.el); const newLastX = event.offsetX / (canvas.clientWidth / canvas.width); const newLastY = event.offsetY / (canvas.clientHeight / canvas.height); this.blurAtPoint(newLastX, newLastY); this.lastX = newLastX; this.lastY = newLastY; }; onStopDraw = () => { if (!this.bluring) { return; } const canvas = unwrap(this.el); this.bluring = false; this.lastX = null; this.lastY = null; this.pushState(canvas.toDataURL()); }; blurAtPoint(_x, _y) { const canvas = unwrap(this.el); const ctx = unwrap(canvas.getContext("2d")); const radius = this.getRadius(); const sigma = this.getSigma(); const x = Math.round(_x); const y = Math.round(_y); const { width, height } = canvas; const startX = Math.max(x - radius, 0); const finishX = Math.max(Math.min(x + radius, width), 0); const startY = Math.max(y - radius, 0); const finishY = Math.max(Math.min(y + radius, height), 0); const newImgWidth = finishX - startX; const newImgHeight = finishY - startY; if (newImgWidth <= 0 || newImgHeight <= 0) { return; } const newImgData = ctx.getImageData( startX, startY, newImgWidth, newImgHeight ); const sourceChannel = []; { const { data } = newImgData; for (let i = 0, l = newImgWidth * newImgHeight; i < l; ++i) { const offset = i * 4; sourceChannel.push( new Pixel(data[offset], data[offset + 1], data[offset + 2]) ); } } const targetChannel = []; gaussBlur(sourceChannel, targetChannel, newImgWidth, newImgHeight, sigma); for (let i = 0, l = newImgWidth * newImgHeight; i < l; ++i) { const { r, g, b } = targetChannel[i]; const offset = i * 4; newImgData.data[offset] = r; newImgData.data[offset + 1] = g; newImgData.data[offset + 2] = b; newImgData.data[offset + 3] = 255; } const bluredPiece = document.createElement("canvas"); bluredPiece.height = newImgData.height; bluredPiece.width = newImgData.width; const bluredPieceCtx = unwrap(bluredPiece.getContext("2d")); bluredPieceCtx.putImageData(newImgData, 0, 0); const pattern = ctx.createPattern(bluredPiece, "no-repeat"); ctx.save(); ctx.beginPath(); if (pattern) { ctx.fillStyle = pattern; } ctx.translate(startX, startY); ctx.arc(x - startX, y - startY, radius, 0, 2 * Math.PI); ctx.fill(); ctx.restore(); } onAfterEnable() { const canvas = unwrap(this.el); canvas.addEventListener("mousedown", this.onStartDraw); canvas.addEventListener("mousemove", this.onProcessDraw); canvas.addEventListener("mouseup", this.onStopDraw); } onBeforeDisable() { const canvas = unwrap(this.el); this.bluring = false; canvas.removeEventListener("mousedown", this.onStartDraw); canvas.removeEventListener("mousemove", this.onProcessDraw); canvas.removeEventListener("mouseup", this.onStopDraw); } }; // src/tools/Filter.ts import { unwrap as unwrap2 } from "krustykrab"; var Filter = class extends Tool { value = 0; applied = false; originalImageData = null; setValue(value) { const newValue = Math.min(Math.max(value, -1), 1); if (newValue === this.value) { return; } this.value = newValue; this.apply(); } getValue() { return this.value; } newImageData() { return unwrap2(this.originalImageData); } apply() { const canvas = unwrap2(this.el); const ctx = unwrap2(canvas.getContext("2d")); ctx.putImageData(this.newImageData(), 0, 0); if (this.applied === false) { this.pushState(canvas.toDataURL()); this.applied = true; } else { this.updateState(canvas.toDataURL()); } } onAfterEnable() { const canvas = unwrap2(this.el); const ctx = unwrap2(canvas.getContext("2d")); const { width, height } = canvas; this.originalImageData = ctx.getImageData(0, 0, width, height); } onBeforeDisable() { this.value = 0; this.applied = false; this.originalImageData = null; } }; // src/tools/Brightness.ts var Brightness = class extends Filter { newImageData = () => { const value = this.getValue(); if (!this.originalImageData) { throw new Error("Image data is not provided"); } const { width, height, data } = this.originalImageData; const imageData = new ImageData(width, height); const newData = imageData.data; const len = data.length; const brightness = Math.round(value * 255); for (let i = 0; i < len; i += 4) { newData[i] = data[i] + brightness; newData[i + 1] = data[i + 1] + brightness; newData[i + 2] = data[i + 2] + brightness; newData[i + 3] = data[i + 3]; } return imageData; }; }; // src/tools/Contrast.ts var Contrast = class extends Filter { newImageData = () => { const value = this.getValue(); if (!this.originalImageData) { throw new Error("Image data is not provided"); } const { width, height, data } = this.originalImageData; const imageData = new ImageData(width, height); const newData = imageData.data; const len = data.length; const contrast = Math.floor(value * 255); const contrastF = 259 * (contrast + 255) / (255 * (259 - contrast)); for (let i = 0; i < len; i += 4) { newData[i] = contrastF * (data[i] - 128) + 128; newData[i + 1] = contrastF * (data[i + 1] - 128) + 128; newData[i + 2] = contrastF * (data[i + 2] - 128) + 128; newData[i + 3] = data[i + 3]; } return imageData; }; }; // src/tools/Crop.ts import { unwrap as unwrap3 } from "krustykrab"; var Crop = class extends Tool { originalImage = null; darkenImage = null; cropping = false; setted = false; startX = null; startY = null; finishX = null; finishY = null; mousedownX = null; mousedownY = null; resizingBorder = null; showCropState() { const canvas = unwrap3(this.el); if (this.startX === null || this.startY === null || this.finishX === null || this.finishY === null) { throw new Error("Coordinates are not setted"); } if (!this.darkenImage || !this.originalImage) { throw new Error("Images are not setted"); } const ctx = unwrap3(canvas.getContext("2d")); const x = Math.min(this.startX, this.finishX); const y = Math.min(this.startY, this.finishY); const width = Math.abs(this.startX - this.finishX); const height = Math.abs(this.startY - this.finishY); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(this.darkenImage, 0, 0, canvas.width, canvas.height); if (height <= 0 || width <= 0) { return; } ctx.clearRect(x, y, width, height); ctx.drawImage(this.originalImage, x, y, width, height, x, y, width, height); } applyCrop() { const canvas = unwrap3(this.el); if (this.startX === null || this.startY === null || this.finishX === null || this.finishY === null) { throw new Error("Coordinates are not setted"); } const x = Math.min(this.startX, this.finishX); const y = Math.min(this.startY, this.finishY); const width = Math.abs(this.startX - this.finishX); const height = Math.abs(this.startY - this.finishY); canvas.width = width; canvas.height = height; const ctx = unwrap3(canvas.getContext("2d")); ctx.drawImage( unwrap3(this.originalImage), x, y, width, height, 0, 0, width, height ); this.pushState(canvas.toDataURL()); this.originalImage = null; this.darkenImage = null; this.disable(); } cancelCrop() { const canvas = unwrap3(this.el); this.startX = null; this.startY = null; this.finishX = null; this.finishY = null; this.setted = false; const ctx = unwrap3(canvas.getContext("2d")); ctx.drawImage(unwrap3(this.darkenImage), 0, 0, canvas.width, canvas.height); this.emit("unset"); this.disable(); } sortCoords() { if (this.startX === null || this.startY === null || this.finishX === null || this.finishY === null) { throw new Error("Coordinates are not setted"); } if (this.startX > this.finishX) { [this.startX, this.finishX] = [this.finishX, this.startX]; } if (this.startY > this.finishY) { [this.startY, this.finishY] = [this.finishY, this.startY]; } } getResizingBorder(x, y) { if (this.startX === null || this.startY === null || this.finishX === null || this.finishY === null) { throw new Error("Coordinates are not setted"); } if (x > this.startX && x < this.finishX) { if (y > this.startY - 5 && y < this.startY + 5) { return "top"; } if (y > this.finishY - 5 && y < this.finishY + 5) { return "bottom"; } } if (y > this.startY && y < this.finishY) { if (x > this.startX - 5 && x < this.startX + 5) { return "left"; } if (x > this.finishX - 5 && x < this.finishX + 5) { return "right"; } } return null; } setCursorForResizingBorder(resizingBorder) { const canvas = unwrap3(this.el); switch (resizingBorder) { case "top": case "bottom": canvas.style.cursor = "row-resize"; break; case "left": case "right": canvas.style.cursor = "col-resize"; break; default: throw new Error(`Unknown border "${resizingBorder}"`); } } onStartDraw = (event) => { const canvas = unwrap3(this.el); const x = Math.min(Math.max(event.offsetX, 0), canvas.clientWidth) / (canvas.clientWidth / canvas.width); const y = Math.min(Math.max(event.offsetY, 0), canvas.clientHeight) / (canvas.clientHeight / canvas.height); if (this.setted) { const resizingBorder = this.getResizingBorder(x, y); if (resizingBorder) { this.resizingBorder = resizingBorder; this.setCursorForResizingBorder(resizingBorder); return; } } this.cropping = true; this.mousedownX = x; this.mousedownY = y; }; onProcessDraw = (event) => { const canvas = unwrap3(this.el); const x = Math.min(Math.max(event.offsetX, 0), canvas.clientWidth) / (canvas.clientWidth / canvas.width); const y = Math.min(Math.max(event.offsetY, 0), canvas.clientHeight) / (canvas.clientHeight / canvas.height); if (this.mousedownX !== null && this.mousedownY !== null) { this.startX = this.mousedownX; this.startY = this.mousedownY; this.mousedownX = null; this.mousedownY = null; } if (this.resizingBorder) { switch (this.resizingBorder) { case "top": this.startY = y; break; case "bottom": this.finishY = y; break; case "left": this.startX = x; break; case "right": this.finishX = x; break; default: throw new Error(`Unknown border "${this.resizingBorder}"`); } this.showCropState(); return; } if (this.cropping) { this.finishX = x; this.finishY = y; this.showCropState(); return; } if (this.setted) { const resizingBorder = this.getResizingBorder(x, y); if (resizingBorder) { this.setCursorForResizingBorder(resizingBorder); } else { canvas.style.removeProperty("cursor"); } } }; onStopDraw = () => { const canvas = unwrap3(this.el); this.mousedownX = null; this.mousedownY = null; if (this.resizingBorder) { this.resizingBorder = null; canvas.style.removeProperty("cursor"); this.sortCoords(); return; } if (this.cropping) { this.sortCoords(); this.cropping = false; this.setted = true; this.emit("set"); } }; onAfterEnable() { const canvas = unwrap3(this.el); const { width, height } = canvas; this.originalImage = document.createElement("canvas"); this.originalImage.width = width; this.originalImage.height = height; const originalContext = unwrap3(this.originalImage.getContext("2d")); originalContext.drawImage(canvas, 0, 0); this.darkenImage = document.createElement("canvas"); this.darkenImage.width = width; this.darkenImage.height = height; const darkenCtx = unwrap3(this.darkenImage.getContext("2d")); darkenCtx.drawImage(canvas, 0, 0); darkenCtx.fillStyle = "rgba(0, 0, 0, 0.4)"; darkenCtx.fillRect(0, 0, width, height); canvas.addEventListener("mousedown", this.onStartDraw); canvas.addEventListener("mousemove", this.onProcessDraw); canvas.addEventListener("mouseup", this.onStopDraw); } onBeforeDisable() { const canvas = unwrap3(this.el); this.cropping = false; this.setted = false; this.resizingBorder = null; canvas.style.removeProperty("cursor"); if (this.darkenImage) { const ctx = unwrap3(canvas.getContext("2d")); ctx.drawImage(unwrap3(this.originalImage), 0, 0); this.originalImage = null; this.darkenImage = null; } canvas.removeEventListener("mousedown", this.onStartDraw); canvas.removeEventListener("mousemove", this.onProcessDraw); canvas.removeEventListener("mouseup", this.onStopDraw); } }; // src/tools/Rectangle.ts import { unwrap as unwrap4 } from "krustykrab"; var Rectangle = class extends Tool { originalImage = null; drawing = false; startX = null; startY = null; finishX = null; finishY = null; showRectangle() { const canvas = unwrap4(this.el); const ctx = unwrap4(canvas.getContext("2d")); if (this.startX === null || this.startY === null || this.finishX === null || this.finishY === null) { throw new Error("Coordinates are not setted"); } const x = Math.min(this.startX, this.finishX); const y = Math.min(this.startY, this.finishY); const width = Math.abs(this.startX - this.finishX); const height = Math.abs(this.startY - this.finishY); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage( unwrap4(this.originalImage), 0, 0, canvas.width, canvas.height ); ctx.beginPath(); ctx.lineWidth = 4; ctx.strokeStyle = "#CB0000"; ctx.rect(x, y, width, height); ctx.stroke(); } onStartDraw = (event) => { const canvas = unwrap4(this.el); const x = event.offsetX / (canvas.clientWidth / canvas.width); const y = event.offsetY / (canvas.clientHeight / canvas.height); this.drawing = true; this.startX = x; this.startY = y; }; onProcessDraw = (event) => { const canvas = unwrap4(this.el); const x = event.offsetX / (canvas.clientWidth / canvas.width); const y = event.offsetY / (canvas.clientHeight / canvas.height); if (this.drawing) { this.finishX = x; this.finishY = y; this.showRectangle(); } }; onStopDraw = () => { if (!this.drawing) { return; } const canvas = unwrap4(this.el); this.drawing = false; const originalCtx = unwrap4(unwrap4(this.originalImage).getContext("2d")); originalCtx.drawImage(canvas, 0, 0); originalCtx.drawImage(canvas, 0, 0); this.pushState(canvas.toDataURL()); }; onAfterEnable() { const canvas = unwrap4(this.el); const { width, height } = canvas; this.originalImage = document.createElement("canvas"); this.originalImage.width = width; this.originalImage.height = height; const originalCtx = unwrap4(this.originalImage.getContext("2d")); originalCtx.drawImage(canvas, 0, 0); canvas.addEventListener("mousedown", this.onStartDraw); canvas.addEventListener("mousemove", this.onProcessDraw); canvas.addEventListener("mouseup", this.onStopDraw); } onBeforeDisable() { const canvas = unwrap4(this.el); this.drawing = false; this.originalImage = null; canvas.removeEventListener("mousedown", this.onStartDraw); canvas.removeEventListener("mousemove", this.onProcessDraw); canvas.removeEventListener("mouseup", this.onStopDraw); } }; // src/tools/RotateLeft.ts import { unwrap as unwrap5 } from "krustykrab"; var RotateLeft = class extends Tool { onAfterEnable() { const canvas = unwrap5(this.el); const ctx = unwrap5(canvas.getContext("2d")); const { width, height } = canvas; const otherCanvas = document.createElement("canvas"); otherCanvas.width = height; otherCanvas.height = width; const otherCtx = unwrap5(otherCanvas.getContext("2d")); otherCtx.clearRect(0, 0, width, height); otherCtx.translate(height / 2, width / 2); otherCtx.rotate(-Math.PI / 2); otherCtx.translate(-width / 2, -height / 2); otherCtx.drawImage(canvas, 0, 0); canvas.width = height; canvas.height = width; ctx.drawImage(otherCanvas, 0, 0); this.pushState(canvas.toDataURL()); this.disable(); } }; // src/tools/RotateRight.ts import { unwrap as unwrap6 } from "krustykrab"; var RotateRight = class extends Tool { onAfterEnable() { const canvas = unwrap6(this.el); const ctx = unwrap6(canvas.getContext("2d")); const { width, height } = canvas; const otherCanvas = document.createElement("canvas"); otherCanvas.width = height; otherCanvas.height = width; const otherCtx = unwrap6(otherCanvas.getContext("2d")); otherCtx.clearRect(0, 0, width, height); otherCtx.translate(height / 2, width / 2); otherCtx.rotate(Math.PI / 2); otherCtx.translate(-width / 2, -height / 2); otherCtx.drawImage(canvas, 0, 0); canvas.width = height; canvas.height = width; ctx.drawImage(otherCanvas, 0, 0); this.pushState(canvas.toDataURL()); this.disable(); } }; export { Blur, Brightness, Contrast, Crop, Rectangle, RotateLeft, RotateRight }; //# sourceMappingURL=index.js.map