diff --git a/Cargo.toml b/Cargo.toml index 2b0afcc..644fa08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,14 @@ version = "0.0.0" crate-type = ["cdylib"] [dependencies] -lodepng = "3.10.4" # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix napi = { version = "2.12.2", default-features = false, features = ["napi4"] } napi-derive = "2.12.2" -sha2 = "0.10.8" + +lodepng = { version = "3.7.0", default-features = false } +flate2 = { package = "flate2", version = "1.0.24", features = ["zlib"], default-features = false } +rgb = "0.8.34" +sha2 = "0.10.6" [build-dependencies] napi-build = "2.0.1" diff --git a/__test__/index.spec.mjs b/__test__/index.spec.mjs index 1ade4ca..388b935 100644 --- a/__test__/index.spec.mjs +++ b/__test__/index.spec.mjs @@ -1,7 +1,22 @@ import test from 'ava' -import { sum } from '../index.js' +import { sum, encodeImage } from '../index.js' +import * as fs from "node:fs"; test('sum from native', (t) => { - t.is(sum(1, 2), 3) + t.is(sum(1, 2), 3) }) + +test('encode random 1', t => { + let buffer = fs.readFileSync('images/6a12c0010c6e708422f5ba5121ba4a8adc9c7374c2b96fd0754c84f25e181598.png'); + let arr = new Uint8Array(buffer); + let encoded = encodeImage(arr); + + let hash = Buffer.from(encoded.hash).toString('hex'); + console.log(hash); + + let mchash = Buffer.from(encoded.minecraftHash).toString('hex'); + console.log(mchash); + + console.log(encoded.hex) +}) \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 2419c97..0ed5883 100644 --- a/index.d.ts +++ b/index.d.ts @@ -4,9 +4,10 @@ /* auto-generated by NAPI-RS */ export declare function sum(a: number, b: number): number -export declare function encodeImage(rawData: Uint8Array): ImageWithHashes +export declare function encodeImage(buffer: Uint8Array): ImageWithHashes export declare class ImageWithHashes { - png: Uint8Array - minecraftHash: Uint8Array - hash: Uint8Array + png: Buffer + minecraftHash: Buffer + hash: Buffer + hex: string } diff --git a/package.json b/package.json index c473db4..6d42cf9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.18.4", - "ava": "^6.0.1" + "ava": "^6.0.1", + "canvas": "^2.11.2" }, "ava": { "timeout": "3m" diff --git a/randomImage.js b/randomImage.js new file mode 100644 index 0000000..cc26a2d --- /dev/null +++ b/randomImage.js @@ -0,0 +1,37 @@ +const fs = require('fs'); +const {createCanvas, loadImage} = require("canvas"); + +function makeRandomImage(width = 64, height = 64) { + const canvas = createCanvas(width, height); + const context = canvas.getContext("2d"); + const imageData = context.getImageData(0, 0, width, height); + + // https://gist.github.com/biovisualize/5400576#file-index-html-L26 + const buffer = new ArrayBuffer(imageData.data.length); + const clampedBuffer = new Uint8ClampedArray(buffer); + const data = new Uint32Array(buffer); + + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + data[y * width + x] = + (255 << 24) | + (((Math.random() * 300) % 255) << 16) | + (((Math.random() * 300) % 255) << 8) | + (((Math.random() * 300) % 255)) + } + } + + imageData.data.set(clampedBuffer); + context.putImageData(imageData, 0, 0); + + // Make Buffer + const dataUrl = canvas.toDataURL("image/png").substr("data:image/png;base64,".length); + const imageBuffer = new Buffer(dataUrl, 'base64'); + const blob = new Blob([imageBuffer], { type: "image/png" }); + return { imageBuffer, imageData, data, blob } +} + +(() => { + const { imageBuffer, imageData, data, blob } = makeRandomImage(64, 64); + fs.writeFileSync('images/randomImage.png', imageBuffer); +})(); \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index a546d1f..0617d33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![deny(clippy::all)] -use lodepng::FilterStrategy; +use lodepng::{FilterStrategy, RGBA}; use sha2::{Digest, Sha256}; use napi::{ bindgen_prelude::{Buffer, ClassInstance, ObjectFinalize, This, Uint8Array, Unknown}, @@ -22,28 +22,40 @@ pub fn sum(a: i32, b: i32) -> i32 { #[napi] pub struct ImageWithHashes { - pub png: Uint8Array, - pub minecraft_hash: Uint8Array, - pub hash: Uint8Array, + pub png: Buffer, + pub minecraft_hash: Buffer, + pub hash: Buffer, + pub hex: String, } #[napi] -pub fn encode_image(raw_data: &[u8]) -> ImageWithHashes { - encode_custom_image(raw_data, SKIN_WIDTH, SKIN_HEIGHT) +pub fn encode_image(buffer: &[u8]) -> ImageWithHashes { + encode_custom_image(buffer, SKIN_WIDTH as usize, SKIN_HEIGHT as usize) } +// based on https://github.com/GeyserMC/global_api/blob/dev/1.0.2/native/skins/src/skin_convert/skin_codec.rs#L100 //#[napi] -pub fn encode_custom_image(raw_data: &[u8], width: u8, height: u8) -> ImageWithHashes { +pub fn encode_custom_image(buffer: &[u8], width: usize, height: usize) -> ImageWithHashes { + let mut raw_data = vec![0; width * height * SKIN_CHANNELS as usize]; + buffer.iter().enumerate().for_each(|(i, byte)| { + raw_data[i] = *byte; + }); + // encode images like Minecraft does let mut encoder = lodepng::Encoder::new(); encoder.set_auto_convert(false); encoder.info_png_mut().interlace_method = 0; // should be 0 but just to be sure - let mut encoder_settings = encoder.settings_mut(); + let encoder_settings = encoder.settings_mut(); encoder_settings.zlibsettings.set_level(4); encoder_settings.filter_strategy = FilterStrategy::ZERO; - let png = encoder.encode(raw_data, width as usize, height as usize).unwrap(); + println!("Encoding image with width: {}, height: {}", width, height); + println!("Raw data length: {}", raw_data.len()); + + let result = encoder.encode(&raw_data, width, height); + println!("Result: {:?}", result); + let png = result.unwrap(); let mut hasher = Sha256::new(); @@ -51,12 +63,23 @@ pub fn encode_custom_image(raw_data: &[u8], width: u8, height: u8) -> ImageWithH let minecraft_hash = hasher.finalize_reset(); // make our own hash - hasher.update(raw_data); + hasher.update(&raw_data); let hash = hasher.finalize(); + let hex = write_hex(minecraft_hash.as_ref()); + ImageWithHashes { - png: Uint8Array::from(png.as_slice()), - minecraft_hash: Uint8Array::from(minecraft_hash.as_slice()), - hash: Uint8Array::from(hash.as_slice()), + png: Buffer::from(png.as_slice()), + minecraft_hash: Buffer::from(minecraft_hash.as_slice()), + hash: Buffer::from(hash.as_slice()), + hex: hex + } +} + +fn write_hex(bytes: &[u8]) -> String { + let mut s = String::with_capacity(2 * bytes.len()); + for byte in bytes { + core::fmt::write(&mut s, format_args!("{:02X}", byte)).unwrap(); } + s } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 161fbdb..23709d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@mapbox/node-pre-gyp@^1.0.5": +"@mapbox/node-pre-gyp@^1.0.0", "@mapbox/node-pre-gyp@^1.0.5": version "1.0.11" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== @@ -248,6 +248,15 @@ callsites@^4.1.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-4.2.0.tgz#98761d5be3ce092e4b9c92f7fb8c8eb9b83cadc8" integrity sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ== +canvas@^2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" + integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.0" + nan "^2.17.0" + simple-get "^3.0.3" + cbor@^9.0.1: version "9.0.2" resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.2.tgz#536b4f2d544411e70ec2b19a2453f10f83cd9fdb" @@ -376,6 +385,13 @@ debug@4, debug@^4.3.4: dependencies: ms "2.1.2" +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -713,6 +729,11 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -755,6 +776,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nan@^2.17.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -794,7 +820,7 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -once@^1.3.0: +once@^1.3.0, once@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -940,6 +966,20 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" + integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + slash@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce"