pixelated images initial experiment

This commit is contained in:
Preet Shihn 2021-02-04 23:27:35 -08:00
parent dc25fe06d0
commit 1156ef6b96
2 changed files with 126 additions and 0 deletions

View File

@ -3453,11 +3453,40 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}
};
private handleCanvasImageDrop = async (
event: React.DragEvent<HTMLCanvasElement>,
file: File,
) => {
try {
const shapes = await (
await import(
/* webpackChunkName: "pixelated-image" */ "../data/pixelated-image"
)
).pixelateImage(file, 20, event.clientX, event.clientY);
const nextElements = [
...this.scene.getElementsIncludingDeleted(),
...shapes,
];
this.scene.replaceAllElements(nextElements);
} catch (error) {
return this.setState({
isLoading: false,
errorMessage: error.message,
});
}
};
private handleCanvasOnDrop = async (
event: React.DragEvent<HTMLCanvasElement>,
) => {
let imageFile: File | null = null;
try {
const file = event.dataTransfer.files[0];
if (file?.type.indexOf("image/") === 0) {
imageFile = file;
}
if (file?.type === "image/png" || file?.type === "image/svg+xml") {
const { elements, appState } = await loadFromBlob(file, this.state);
this.syncActionResult({
@ -3469,8 +3498,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
commitToHistory: true,
});
return;
} else if (imageFile) {
return await this.handleCanvasImageDrop(event, imageFile);
}
} catch (error) {
if (imageFile) {
return await this.handleCanvasImageDrop(event, imageFile);
}
return this.setState({
isLoading: false,
errorMessage: error.message,

View File

@ -0,0 +1,92 @@
import { ExcalidrawGenericElement, NonDeleted } from "../element/types";
import { newElement } from "../element";
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from "../constants";
import { randomId } from "../random";
const loadImage = async (url: string): Promise<HTMLImageElement> => {
const image = new Image();
return new Promise<HTMLImageElement>((resolve, reject) => {
image.onload = () => resolve(image);
image.onerror = (err) =>
reject(
new Error(
`Failed to load image: ${err ? err.toString : "unknown error"}`,
),
);
image.onabort = () =>
reject(new Error(`Failed to load image: image load aborted`));
image.src = url;
});
};
const commonProps = {
fillStyle: "solid",
fontFamily: DEFAULT_FONT_FAMILY,
fontSize: DEFAULT_FONT_SIZE,
opacity: 100,
roughness: 1,
strokeColor: "transparent",
strokeSharpness: "sharp",
strokeStyle: "solid",
strokeWidth: 1,
verticalAlign: "middle",
} as const;
export const pixelateImage = async (
blob: Blob,
cellSize: number,
x: number,
y: number,
) => {
const url = URL.createObjectURL(blob);
try {
const image = await loadImage(url);
// initialize canvas for pixelation
const { width, height } = image;
const canvasWidth = Math.floor(width / cellSize);
const canvasHeight = Math.floor(height / cellSize);
const canvas =
"OffscreenCanvas" in window
? new OffscreenCanvas(canvasWidth, canvasHeight)
: document.createElement("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Draw image on canvas
const ctx = canvas.getContext("2d")!;
ctx.drawImage(image, 0, 0, width, height, 0, 0, canvasWidth, canvasHeight);
const imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
const buffer = imageData.data;
const groupId = randomId();
const shapes: NonDeleted<ExcalidrawGenericElement>[] = [];
for (let row = 0; row < canvasHeight; row++) {
for (let col = 0; col < canvasWidth; col++) {
const offset = row * canvasWidth * 4 + col * 4;
const r = buffer[offset];
const g = buffer[offset + 1];
const b = buffer[offset + 2];
const alpha = buffer[offset + 3];
if (alpha) {
const color = `rgba(${r}, ${g}, ${b}, ${alpha})`;
const rectangle = newElement({
backgroundColor: color,
groupIds: [groupId],
...commonProps,
type: "rectangle",
x: x + col * cellSize,
y: y + row * cellSize,
width: cellSize,
height: cellSize,
});
shapes.push(rectangle);
}
}
}
return shapes;
} finally {
URL.revokeObjectURL(url);
}
};