Add mural compression
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import React from "react";
|
||||
import { Store } from "../../lib/store";
|
||||
import { Mural, MuralEx, MuralStatus } from "../../interfaces";
|
||||
import { MuralEx, MuralOld, MuralStatus } from "../../interfaces";
|
||||
import styled from "styled-components";
|
||||
import { A, Border, Btn, Flex, SELECTED_COLOR } from "../styles";
|
||||
import { CanvasToCanvasJSX } from "./canvasToCanvasJSX";
|
||||
import { formatNumber, getMuralHeight, getMuralWidth, getPixelStatusMural } from "../../lib/utils";
|
||||
import { convertOldMuralToNewMural, formatNumber, getMuralHeight, getMuralWidth, getPixelStatusMural } from "../../lib/utils";
|
||||
import {
|
||||
IconDefinition, faDownload, faLayerGroup, faLocation, faPenToSquare, faRefresh, faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -14,7 +14,7 @@ import { BIN_FORMATS } from "../importMural";
|
||||
import saveAs from "file-saver";
|
||||
import { Popup } from "./Popup";
|
||||
import { Palette } from "../../lib/palette";
|
||||
import { serializeMural } from "../serializer";
|
||||
import { Mural } from "../../lib/mural";
|
||||
|
||||
const Margin = styled.div`
|
||||
margin: 2px;
|
||||
@@ -212,14 +212,15 @@ export class MuralView extends React.Component<Props, State> {
|
||||
// this.props.store.updateMural(this.props.mural);
|
||||
// }
|
||||
};
|
||||
onExport = () => {
|
||||
const rawMural: Mural = {
|
||||
onExport = async () => {
|
||||
const rawMural: MuralOld = {
|
||||
name: this.props.mural.name,
|
||||
x: this.props.mural.x,
|
||||
y: this.props.mural.y,
|
||||
pixels: this.props.mural.pixels,
|
||||
};
|
||||
const buffer = serializeMural(rawMural);
|
||||
const mural = convertOldMuralToNewMural(rawMural);
|
||||
const buffer = await mural.getBuffer();
|
||||
const blob = new Blob([buffer], { type: "octet/stream" });
|
||||
|
||||
const saveName = rawMural.name
|
||||
|
||||
+35
-9
@@ -1,6 +1,6 @@
|
||||
|
||||
import React from "react";
|
||||
import { Mural, RGB } from "../interfaces";
|
||||
import { MuralOld, RGB } from "../interfaces";
|
||||
import { canvasToImageData, get2DArrHeight, get2DArrWidth, getColorScore, getExtension,
|
||||
imageDataToPaletteIndices, imageToCanvas, loadImageSource, processNumberEvent,
|
||||
readAsArrayBuffer,
|
||||
@@ -14,7 +14,7 @@ import { Palette } from "../lib/palette";
|
||||
import { FileInput } from "../lib/fileinput";
|
||||
import { Store } from "../lib/store";
|
||||
import { Coordinates } from "../lib/coordinates";
|
||||
import { deserializeMural } from "./serializer";
|
||||
import { Mural } from "../lib/mural";
|
||||
|
||||
export const TEXT_FORMATS = ["muraljson", "json"];
|
||||
export const BIN_FORMATS = ["pcm", "bin"];
|
||||
@@ -89,11 +89,11 @@ export async function importArtWork(store: Store, cords: Coordinates, palette: P
|
||||
const file = await importFile();
|
||||
if (file) {
|
||||
if (file.type === "mural") {
|
||||
return file.data as Mural;
|
||||
return file.data as MuralOld;
|
||||
} else {
|
||||
const img = file.data as HTMLImageElement;
|
||||
const pixels = await imageToMural(img, palette);
|
||||
const mural = await new Promise<Mural>((resolve, reject)=> {
|
||||
const mural = await new Promise<MuralOld>((resolve, reject)=> {
|
||||
store.setOverlayModify({
|
||||
pixels,
|
||||
muralObj: {
|
||||
@@ -483,23 +483,49 @@ async function importFile() {
|
||||
const name = ex.text;
|
||||
if (TEXT_FORMATS.includes(etn)) {
|
||||
const content = await readAsString(fileData);
|
||||
const mural = JSON.parse(content) as Mural;
|
||||
const mural = JSON.parse(content) as MuralOld;
|
||||
if (!mural.name) {
|
||||
mural.name = await Popup
|
||||
.prompt("Missing name for this mural. Please enter it manually", name) || "";
|
||||
}
|
||||
validateMural(mural);
|
||||
|
||||
return {
|
||||
type: "mural",
|
||||
data: mural,
|
||||
};
|
||||
} else if (BIN_FORMATS.includes(etn)) {
|
||||
const buffer = await readAsArrayBuffer(fileData);
|
||||
const mural = deserializeMural(buffer);
|
||||
validateMural(mural);
|
||||
const mural = await Mural.from(new Uint8Array(buffer));
|
||||
const pixelBuffer = mural.pixelBuffer;
|
||||
const pixels = Array.from({ length: mural.h }, () => Array(mural.w).fill(0));
|
||||
let i = 0;
|
||||
let yy = -1;
|
||||
leg:
|
||||
for (;;) {
|
||||
yy++;
|
||||
if (pixelBuffer[i] === undefined) {
|
||||
break;
|
||||
}
|
||||
// const ref = [];
|
||||
// pixels.push(ref);
|
||||
for (let xx = 0; xx < mural.w; xx++) {
|
||||
const item = pixelBuffer[i++];
|
||||
if (item != undefined) {
|
||||
pixels[yy][xx] = item;
|
||||
if(item > 15) {
|
||||
console.log(item);
|
||||
}
|
||||
//ref.push(item);
|
||||
} else {
|
||||
break leg;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "mural",
|
||||
data: mural,
|
||||
data: {
|
||||
name, x: mural.x, y: mural.y, pixels,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const readData = await readAsDataUrl(fileData);
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { Mural } from "../interfaces";
|
||||
|
||||
const MAGIC_REPEATING = 200;
|
||||
const TRANSPARENT = 201;
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
export function compressRepeatingArray(index: number, buffer: number[] /* 0xFF */) {
|
||||
const a = buffer[index];
|
||||
const b = buffer[index + 1];
|
||||
const c = buffer[index + 2];
|
||||
const pixelIndex = a === -1 ? TRANSPARENT : a;
|
||||
if (a === b && b === c) {
|
||||
let i = 0;
|
||||
for (; i < Math.min(buffer.length - index, 0xFF); i++) {
|
||||
if (a !== buffer[index + i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return [MAGIC_REPEATING, i - 1, pixelIndex];
|
||||
}
|
||||
return [pixelIndex];
|
||||
}
|
||||
|
||||
export function decompressRepeatingArray(buffer: ArrayLike<number>) {
|
||||
const output: number[] = [];
|
||||
const addValue = (value: number) => {
|
||||
output.push(value === TRANSPARENT ? -1 : value);
|
||||
};
|
||||
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
const item = buffer[i];
|
||||
if (item === MAGIC_REPEATING) {
|
||||
const length = buffer[++i];
|
||||
const value = buffer[++i];
|
||||
for (let j = 0; j < length; j++) {
|
||||
addValue(value);
|
||||
}
|
||||
} else {
|
||||
addValue(item);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export function serializeMural(mural: Mural) {
|
||||
const encoder = new TextEncoder();
|
||||
const nameBinary = encoder.encode(mural.name);
|
||||
const array: number[] = [];
|
||||
for (let i = 0; i < mural.pixels.length; i++) {
|
||||
for (let j = 0; j < mural.pixels[i].length; j++) {
|
||||
array.push(mural.pixels[i][j]);
|
||||
}
|
||||
}
|
||||
const pixelEncodeBuffer: number[] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const rtn = compressRepeatingArray(i, array);
|
||||
if (rtn.length === 1) {
|
||||
pixelEncodeBuffer.push(rtn[0]);
|
||||
} else {
|
||||
pixelEncodeBuffer.push(rtn[0], rtn[1], rtn[2]);
|
||||
i += rtn[1] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
const pixelBuffer = new Uint8Array(pixelEncodeBuffer);
|
||||
const firstHalf = 1 + 4 + 4 + 1 + 2 + nameBinary.byteLength;
|
||||
const buffer = new ArrayBuffer(firstHalf + pixelBuffer.byteLength);
|
||||
const view = new DataView(buffer);
|
||||
view.setUint8(0, VERSION);
|
||||
view.setInt32(1, mural.x, true);
|
||||
view.setInt32(5, mural.y, true);
|
||||
view.setUint16(9, mural.pixels[0].length, true);
|
||||
view.setUint8(11, nameBinary.length);
|
||||
new Uint8Array(buffer).set(nameBinary, 12);
|
||||
new Uint8Array(buffer).set(pixelBuffer, firstHalf);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function deserializeMural(buffer: ArrayBuffer): Mural {
|
||||
const view = new DataView(buffer);
|
||||
const version = view.getUint8(0);
|
||||
if (version !== VERSION) {
|
||||
throw new Error("Unknown version");
|
||||
}
|
||||
const x = view.getInt32(1, true);
|
||||
const y = view.getInt32(5, true);
|
||||
const width = view.getUint16(9, true);
|
||||
const nameLength = view.getUint8(11);
|
||||
|
||||
const nameBytes = new Uint8Array(buffer, 12, nameLength);
|
||||
const decoder = new TextDecoder();
|
||||
const name = decoder.decode(nameBytes);
|
||||
const pixelBuffer = new Uint8Array(buffer, 12 + nameLength);
|
||||
const pixelArray: number[] = decompressRepeatingArray(pixelBuffer);
|
||||
|
||||
const pixels: number[][] = [];
|
||||
|
||||
leg:
|
||||
for (;;) {
|
||||
if (!pixelArray.length) {
|
||||
break;
|
||||
}
|
||||
const ref: number[] = [];
|
||||
pixels.push(ref);
|
||||
for (let j = 0; j < width; j++) {
|
||||
const item = pixelArray.shift();
|
||||
if (item != undefined) {
|
||||
ref.push(item);
|
||||
} else {
|
||||
break leg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
pixels
|
||||
} as Mural;
|
||||
}
|
||||
Reference in New Issue
Block a user