Merge pull request #45 from bs-community/split-utils

Split utils.ts into a separate package
This commit is contained in:
Haowei Wen 2020-01-19 18:21:30 +08:00 committed by GitHub
commit 5e16943ad2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 22 additions and 5928 deletions

View File

@ -1,58 +0,0 @@
/* eslint-env node */
process.env.CHROME_BIN = require("puppeteer").executablePath();
module.exports = function (config) {
config.set({
basePath: "",
frameworks: ["mocha"],
files: [
"test/test.ts"
],
exclude: [],
preprocessors: {
"test/test.ts": ["webpack"]
},
webpack: {
mode: "development",
module: {
rules: [
{
test: /\.png$/i,
loader: "url-loader"
},
{
test: /\.ts$/,
loader: "ts-loader",
options: {
transpileOnly: true
}
}
]
},
resolve: {
extensions: [".ts", ".js", ".json"]
}
},
webpackMiddleware: {
stats: "errors-only"
},
mime: {
"text/x-typescript": ["ts"]
},
reporters: ["progress"],
port: 9876,
colors: true,
logLevel: config.LOG_WARN,
autoWatch: false,
browsers: ["ChromeHeadlessNoSandbox"],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: "ChromeHeadless",
flags: ["--no-sandbox"]
}
},
singleRun: true,
concurrency: Infinity
});
};

5601
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
"main": "dist/skinview3d.js",
"scripts": {
"build": "rollup -c",
"test": "karma start && npm run lint",
"test": "npm run lint",
"prepublishOnly": "rimraf dist && npm run build",
"lint": "tslint --formatters-dir ./node_modules/tslint-formatter-beauty -t beauty -p .",
"dev": "npm-run-all --parallel watch serve",
@ -33,31 +33,20 @@
"dist"
],
"dependencies": {
"three": "^0.112.1"
"three": "^0.112.1",
"skinview-utils": "^0.2.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^6.0.0",
"@types/chai": "^4.1.4",
"@types/mocha": "^5.2.5",
"chai": "^4.2.0",
"karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"karma-webpack": "^3.0.0",
"local-web-server": "^2.6.0",
"mocha": "^5.2.0",
"npm-run-all": "^4.1.3",
"puppeteer": "^1.7.0",
"rimraf": "^2.6.2",
"rollup": "^1.27.14",
"rollup-plugin-license": "^0.13.0",
"rollup-plugin-terser": "^5.1.3",
"rollup-plugin-typescript2": "^0.25.3",
"ts-loader": "^5.2.2",
"tslint": "^5.11.0",
"tslint-formatter-beauty": "^3.0.0-beta.2",
"typescript": "^3.7.4",
"url-loader": "^1.1.1",
"webpack": "^4.41.4"
"typescript": "^3.7.4"
}
}

View File

@ -30,7 +30,3 @@ export {
RunningAnimation,
RotatingAnimation
} from "./animation";
export {
isSlimSkin
} from "./utils";

View File

@ -1,192 +0,0 @@
function copyImage(context: CanvasRenderingContext2D, sX: number, sY: number, w: number, h: number, dX: number, dY: number, flipHorizontal: boolean) {
const imgData = context.getImageData(sX, sY, w, h);
if (flipHorizontal) {
for (let y = 0; y < h; y++) {
for (let x = 0; x < (w / 2); x++) {
const index = (x + y * w) * 4;
const index2 = ((w - x - 1) + y * w) * 4;
const pA1 = imgData.data[index];
const pA2 = imgData.data[index + 1];
const pA3 = imgData.data[index + 2];
const pA4 = imgData.data[index + 3];
const pB1 = imgData.data[index2];
const pB2 = imgData.data[index2 + 1];
const pB3 = imgData.data[index2 + 2];
const pB4 = imgData.data[index2 + 3];
imgData.data[index] = pB1;
imgData.data[index + 1] = pB2;
imgData.data[index + 2] = pB3;
imgData.data[index + 3] = pB4;
imgData.data[index2] = pA1;
imgData.data[index2 + 1] = pA2;
imgData.data[index2 + 2] = pA3;
imgData.data[index2 + 3] = pA4;
}
}
}
context.putImageData(imgData, dX, dY);
}
function hasTransparency(context: CanvasRenderingContext2D, x0: number, y0: number, w: number, h: number) {
const imgData = context.getImageData(x0, y0, w, h);
for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
const offset = (x + y * w) * 4;
if (imgData.data[offset + 3] !== 0xff) {
return true;
}
}
}
return false;
}
function computeSkinScale(width: number) {
return width / 64.0;
}
function fixOpaqueSkin(context: CanvasRenderingContext2D, width: number) {
// Some ancient skins don't have transparent pixels (nor have helm).
// We have to make the helm area transparent, otherwise it will be rendered as black.
if (!hasTransparency(context, 0, 0, width, width / 2)) {
const scale = computeSkinScale(width);
const clearArea = (x, y, w, h) => context.clearRect(x * scale, y * scale, w * scale, h * scale);
clearArea(40, 0, 8, 8); // Helm Top
clearArea(48, 0, 8, 8); // Helm Bottom
clearArea(32, 8, 8, 8); // Helm Right
clearArea(40, 8, 8, 8); // Helm Front
clearArea(48, 8, 8, 8); // Helm Left
clearArea(56, 8, 8, 8); // Helm Back
}
}
function convertSkinTo1_8(context: CanvasRenderingContext2D, width: number) {
const scale = computeSkinScale(width);
const copySkin = (sX, sY, w, h, dX, dY, flipHorizontal) => copyImage(context, sX * scale, sY * scale, w * scale, h * scale, dX * scale, dY * scale, flipHorizontal);
fixOpaqueSkin(context, width);
copySkin(4, 16, 4, 4, 20, 48, true); // Top Leg
copySkin(8, 16, 4, 4, 24, 48, true); // Bottom Leg
copySkin(0, 20, 4, 12, 24, 52, true); // Outer Leg
copySkin(4, 20, 4, 12, 20, 52, true); // Front Leg
copySkin(8, 20, 4, 12, 16, 52, true); // Inner Leg
copySkin(12, 20, 4, 12, 28, 52, true); // Back Leg
copySkin(44, 16, 4, 4, 36, 48, true); // Top Arm
copySkin(48, 16, 4, 4, 40, 48, true); // Bottom Arm
copySkin(40, 20, 4, 12, 40, 52, true); // Outer Arm
copySkin(44, 20, 4, 12, 36, 52, true); // Front Arm
copySkin(48, 20, 4, 12, 32, 52, true); // Inner Arm
copySkin(52, 20, 4, 12, 44, 52, true); // Back Arm
}
export function loadSkinToCanvas(canvas: HTMLCanvasElement, image: HTMLImageElement) {
let isOldFormat = false;
if (image.width !== image.height) {
if (image.width === 2 * image.height) {
isOldFormat = true;
} else {
throw new Error(`Bad skin size: ${image.width}x${image.height}`);
}
}
const context = canvas.getContext("2d")!;
if (isOldFormat) {
const sideLength = image.width;
canvas.width = sideLength;
canvas.height = sideLength;
context.clearRect(0, 0, sideLength, sideLength);
context.drawImage(image, 0, 0, sideLength, sideLength / 2.0);
convertSkinTo1_8(context, sideLength);
} else {
canvas.width = image.width;
canvas.height = image.height;
context.clearRect(0, 0, image.width, image.height);
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
}
export function loadCapeToCanvas(canvas: HTMLCanvasElement, image: HTMLImageElement) {
let isOldFormat = false;
if (image.width !== 2 * image.height) {
if (image.width * 17 === image.height * 22) {
// width/height = 22/17
isOldFormat = true;
} else {
throw new Error(`Bad cape size: ${image.width}x${image.height}`);
}
}
const context = canvas.getContext("2d")!;
if (isOldFormat) {
const width = image.width * 64 / 22;
canvas.width = width;
canvas.height = width / 2;
} else {
canvas.width = image.width;
canvas.height = image.height;
}
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(image, 0, 0, image.width, image.height);
}
export function isSlimSkin(canvasOrImage: HTMLCanvasElement | HTMLImageElement): boolean {
// Detects whether the skin is default or slim.
//
// The right arm area of *default* skins:
// (44,16)->*-------*-------*
// (40,20) |top |bottom |
// \|/ |4x4 |4x4 |
// *-------*-------*-------*-------*
// |right |front |left |back |
// |4x12 |4x12 |4x12 |4x12 |
// *-------*-------*-------*-------*
// The right arm area of *slim* skins:
// (44,16)->*------*------*-*
// (40,20) |top |bottom| |<----[x0=50,y0=16,w=2,h=4]
// \|/ |3x4 |3x4 | |
// *-------*------*------***-----*-*
// |right |front |left |back | |<----[x0=54,y0=20,w=2,h=12]
// |4x12 |3x12 |4x12 |3x12 | |
// *-------*------*-------*------*-*
// Compared with default right arms, slim right arms have 2 unused areas.
//
// The same is true for left arm:
// The left arm area of *default* skins:
// (36,48)->*-------*-------*
// (32,52) |top |bottom |
// \|/ |4x4 |4x4 |
// *-------*-------*-------*-------*
// |right |front |left |back |
// |4x12 |4x12 |4x12 |4x12 |
// *-------*-------*-------*-------*
// The left arm area of *slim* skins:
// (36,48)->*------*------*-*
// (32,52) |top |bottom| |<----[x0=42,y0=48,w=2,h=4]
// \|/ |3x4 |3x4 | |
// *-------*------*------***-----*-*
// |right |front |left |back | |<----[x0=46,y0=52,w=2,h=12]
// |4x12 |3x12 |4x12 |3x12 | |
// *-------*------*-------*------*-*
//
// If there is a transparent pixel in any of the 4 unused areas, the skin must be slim,
// as transparent pixels are not allowed in the first layer.
if (canvasOrImage instanceof HTMLCanvasElement) {
const canvas = canvasOrImage;
const scale = computeSkinScale(canvas.width);
const context = canvas.getContext("2d")!;
const checkArea = (x, y, w, h) => hasTransparency(context, x * scale, y * scale, w * scale, h * scale);
return checkArea(50, 16, 2, 4) ||
checkArea(54, 20, 2, 12) ||
checkArea(42, 48, 2, 4) ||
checkArea(46, 52, 2, 12);
} else {
const image = canvasOrImage;
const canvas = document.createElement("canvas");
loadSkinToCanvas(canvas, image);
return isSlimSkin(canvas);
}
}

View File

@ -1,7 +1,7 @@
import { DoubleSide, FrontSide, MeshBasicMaterial, NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
import { RootAnimation } from "./animation";
import { PlayerObject } from "./model";
import { isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "./utils";
import { isSlimSkin, loadCapeToCanvas, loadSkinToCanvas } from "skinview-utils";
export interface SkinViewerOptions {
domElement: Node;

View File

@ -1,4 +0,0 @@
extends: ../.eslintrc.js
env:
mocha: true
browser: true

4
test/shims.d.ts vendored
View File

@ -1,4 +0,0 @@
declare module "*.png" {
let content: any;
export default content;
}

View File

@ -1,52 +0,0 @@
/// <reference path="shims.d.ts"/>
import { expect } from "chai";
import { isSlimSkin, loadSkinToCanvas } from "../src/utils";
import skin1_8Default from "./textures/skin-1.8-default-no_hd.png";
import skin1_8Slim from "./textures/skin-1.8-slim-no_hd.png";
import skinOldDefault from "./textures/skin-old-default-no_hd.png";
import skinLegacyHatDefault from "./textures/skin-legacyhat-default-no_hd.png";
describe("detect model of texture", () => {
it("1.8 default", async () => {
const image = document.createElement("img");
image.src = skin1_8Default;
await Promise.resolve();
expect(isSlimSkin(image)).to.equal(false);
});
it("1.8 slim", async () => {
const image = document.createElement("img");
image.src = skin1_8Slim;
await Promise.resolve();
expect(isSlimSkin(image)).to.equal(true);
});
it("old default", async () => {
const image = document.createElement("img");
image.src = skinOldDefault;
await Promise.resolve();
expect(isSlimSkin(image)).to.equal(false);
});
});
describe("process skin texture", () => {
it("clear the hat area of legacy skin", async () => {
const image = document.createElement("img");
image.src = skinLegacyHatDefault;
await Promise.resolve();
const canvas = document.createElement("canvas");
loadSkinToCanvas(canvas, image);
const data = canvas.getContext("2d")!.getImageData(0, 0, 64, 32).data;
const checkArea = (x0, y0, w, h) => {
for (let x = x0; x < x0 + w; x++) {
for (let y = y0; y < y0 + h; y++) {
expect(data[(y * 64 + x) * 4 + 3]).to.equal(0);
}
}
};
checkArea(40, 0, 8 * 2, 8); // top + bottom
checkArea(32, 8, 8 * 4, 8) // right + front + left + back
});
});

View File

@ -1,5 +0,0 @@
|File|Original author|Description|
|----|---------------|-----------|
|`skin-1.8-default-no_hd.png`|ElizaMozi|[Source](http://www.mcbbs.net/thread-705557-1-1.html)(Chinese); [Approval of use](http://www.mcbbs.net/forum.php?mod=viewthread&tid=705557&page=50#pid12693907)(Chinese)|
|`skin-1.8-slim-no_hd.png` |ElizaMozi|Same as above.|
|`skin-old-default-no_hd.png`|Hacksore ||

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

View File

@ -1,9 +1,5 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:latest"
],
"jsRules": {},
"extends": "tslint:latest",
"rules": {
"curly": false,
"ordered-imports": false,
@ -27,6 +23,5 @@
"max-classes-per-file": false,
"interface-name": false,
"member-ordering": false
},
"rulesDirectory": []
}
}