Add binary save and load

This commit is contained in:
2025-02-09 18:48:39 +01:00
parent 46d5e7521f
commit 8d5cdf79d8
4 changed files with 44 additions and 11 deletions
+10 -3
View File
@@ -10,10 +10,11 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Coordinates, CordType } from "../../lib/coordinates";
import { TEXT_FORMATS } from "../importMural";
import { BIN_FORMATS } from "../importMural";
import saveAs from "file-saver";
import { Popup } from "./Popup";
import { Palette } from "../../lib/palette";
import { serializeMural } from "../serializer";
const Margin = styled.div`
margin: 2px;
@@ -218,8 +219,14 @@ export class MuralView extends React.Component<Props, State> {
y: this.props.mural.y,
pixels: this.props.mural.pixels,
};
const blob = new Blob([JSON.stringify(rawMural)], { type: "application/json;charset=utf-8" });
saveAs(blob, `${rawMural.name}.${TEXT_FORMATS[0]}`);
const buffer = serializeMural(rawMural);
const blob = new Blob([buffer], { type: "octet/stream" });
const saveName = rawMural.name
.replace(/ /, "_")
.replace(/[^a-zA-Z0-9-_]/g, "");
saveAs(blob, `${saveName}.${BIN_FORMATS[0]}`);
};
onDelete = async () => {
if (await Popup.confirm(`Are you sure you want to remove "${this.props.mural.name}"`)) {
+18 -2
View File
@@ -3,6 +3,7 @@ import React from "react";
import { Mural, RGB } from "../interfaces";
import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension,
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
readAsArrayBuffer,
readAsDataUrl, readAsString, resize, rgb, validateMural } from "../lib/utils";
import styled from "styled-components";
import { Popup } from "./components/Popup";
@@ -13,8 +14,14 @@ import { Palette } from "../lib/palette";
import { FileInput } from "../lib/fileinput";
import { Store } from "../lib/store";
import { Coordinates } from "../lib/coordinates";
import { deserializeMural } from "./serializer";
export const TEXT_FORMATS = ["muraljson", "json"];
export const BIN_FORMATS = ["pcm", "bin"];
// TODO: Add bundle
export const BIN_BUNDLE_FORMATS = ["pcmb"];
export const ZIP_FORMATS = ["zip"];
enum RetrieveType {
@@ -465,15 +472,16 @@ export function imageDataToCanvas(imageData: ImageData) {
async function importFile() {
const fileInput = new FileInput();
fileInput.setAcceptType(["png", "jpg", "jpeg", ...TEXT_FORMATS]);
fileInput.setAcceptType(["png", "jpg", "jpeg", ...TEXT_FORMATS, ...BIN_FORMATS]);
const files = await fileInput.show();
const fileData = files[0];
if (!fileData) {
throw new Error("Empty file");
}
const ex = getExtension(fileData.name);
const etn = ex.ex.toLowerCase();
const name = ex.text;
if (TEXT_FORMATS.includes(ex.ex)) {
if (TEXT_FORMATS.includes(etn)) {
const content = await readAsString(fileData);
const mural = JSON.parse(content) as Mural;
if (!mural.name) {
@@ -485,6 +493,14 @@ async function importFile() {
type: "mural",
data: mural,
};
} else if (BIN_FORMATS.includes(etn)) {
const buffer = await readAsArrayBuffer(fileData);
const mural = deserializeMural(buffer);
validateMural(mural);
return {
type: "mural",
data: mural,
};
} else {
const readData = await readAsDataUrl(fileData);
return {
+2 -6
View File
@@ -1,5 +1,4 @@
import { Mural } from "../interfaces";
import { waitForDraw } from "../lib/utils";
const MAGIC_REPEATING = 200;
const TRANSPARENT = 201;
@@ -44,10 +43,9 @@ export function decompressRepeatingArray(buffer: ArrayLike<number>) {
return output;
}
export async function serializeMural(mural: Mural) {
export function serializeMural(mural: Mural) {
const encoder = new TextEncoder();
const nameBinary = encoder.encode(mural.name);
await waitForDraw();
const array: number[] = [];
for (let i = 0; i < mural.pixels.length; i++) {
for (let j = 0; j < mural.pixels[i].length; j++) {
@@ -80,7 +78,7 @@ export async function serializeMural(mural: Mural) {
return buffer;
}
export async function deserializeMural(buffer: ArrayBuffer): Promise<Mural> {
export function deserializeMural(buffer: ArrayBuffer): Mural {
const view = new DataView(buffer);
const version = view.getUint8(0);
if (version !== VERSION) {
@@ -95,14 +93,12 @@ export async function deserializeMural(buffer: ArrayBuffer): Promise<Mural> {
const decoder = new TextDecoder();
const name = decoder.decode(nameBytes);
const pixelBuffer = new Uint8Array(buffer, 12 + nameLength);
await waitForDraw();
const pixelArray: number[] = decompressRepeatingArray(pixelBuffer);
const pixels: number[][] = [];
leg:
for (;;) {
await waitForDraw();
if (!pixelArray.length) {
break;
}
+14
View File
@@ -91,6 +91,20 @@ export function readAsDataUrl(blob: Blob) {
});
}
export function readAsArrayBuffer(blob: Blob) {
return new Promise<ArrayBuffer >((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
resolve(reader.result as ArrayBuffer );
});
reader.addEventListener("error", err => {
reject(err);
});
reader.readAsArrayBuffer(blob);
});
}
export function loadImageSource(name: string, data: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const image = new Image();