diff --git a/src/app/avatar/AvatarImage.ts b/src/app/avatar/AvatarImage.ts index c77ff56..66cac9a 100644 --- a/src/app/avatar/AvatarImage.ts +++ b/src/app/avatar/AvatarImage.ts @@ -305,6 +305,8 @@ export class AvatarImage implements IAvatarImage partCount--; } + //CanvasUtilities.cropTransparentPixels(canvas); + //if(this._avatarSpriteData && this._avatarSpriteData.paletteIsGrayscale) this.convertToGrayscale(container); return canvas; diff --git a/src/app/avatar/cache/AvatarImageCache.ts b/src/app/avatar/cache/AvatarImageCache.ts index 30c35ea..e16f374 100644 --- a/src/app/avatar/cache/AvatarImageCache.ts +++ b/src/app/avatar/cache/AvatarImageCache.ts @@ -12,6 +12,7 @@ import { AvatarCanvas } from '../structure'; import { AvatarImageActionCache } from './AvatarImageActionCache'; import { AvatarImageBodyPartCache } from './AvatarImageBodyPartCache'; import { AvatarImageDirectionCache } from './AvatarImageDirectionCache'; +import { CompleteImageData } from './CompleteImageData'; import { ImageData } from './ImageData'; export class AvatarImageCache @@ -416,7 +417,7 @@ export class AvatarImageCache imageIndex--; } - return new AvatarImageBodyPartContainer(imageData.texture, offset, isCacheable); + return new AvatarImageBodyPartContainer(imageData.image, offset, isCacheable); } private convertColorToHex(k: number): string @@ -429,7 +430,7 @@ export class AvatarImageCache return _local_2; } - private createUnionImage(imageDatas: ImageData[], isFlipped: boolean): ImageData + private createUnionImage(imageDatas: ImageData[], isFlipped: boolean): CompleteImageData { const bounds = new Rectangle(); @@ -470,10 +471,11 @@ export class AvatarImageCache ty = (regPoint.y - data.rect.y); } + const tintedTexture = texture.getTintedWithMultiply(color); + ctx.save(); - //ctx.fillStyle = ('#' + this.convertColorToHex(data.colorTransform)); ctx.transform(scale, 0, 0, 1, tx, ty); - ctx.drawImage(texture, 0, 0, data.rect.width, data.rect.height,); + ctx.drawImage(tintedTexture, 0, 0, data.rect.width, data.rect.height); ctx.restore(); // set the color @@ -481,6 +483,6 @@ export class AvatarImageCache //console.log(); } - return new ImageData(canvas, new Rectangle(0, 0, canvas.width, canvas.height), point, isFlipped, null); + return new CompleteImageData(canvas, new Rectangle(0, 0, canvas.width, canvas.height), point, isFlipped, null); } } diff --git a/src/app/avatar/cache/CompleteImageData.ts b/src/app/avatar/cache/CompleteImageData.ts new file mode 100644 index 0000000..5e3b63b --- /dev/null +++ b/src/app/avatar/cache/CompleteImageData.ts @@ -0,0 +1,59 @@ +import { Canvas } from 'canvas'; +import { Point, Rectangle } from '../../../core'; + +export class CompleteImageData +{ + private _image: Canvas; + private _rect: Rectangle; + private _regPoint: Point; + private _flipH: boolean; + private _colorTransform: number; + + constructor(texture: Canvas, rectangle: Rectangle, _arg_3: Point, flipH: boolean, color: number) + { + this._image = texture; + this._rect = rectangle; + this._regPoint = _arg_3; + this._flipH = flipH; + this._colorTransform = color; + + if(flipH) this._regPoint.x = (-(this._regPoint.x) + rectangle.width); + } + + public dispose(): void + { + this._image = null; + this._regPoint = null; + this._colorTransform = null; + } + + public get image(): Canvas + { + return this._image; + } + + public get rect(): Rectangle + { + return this._rect; + } + + public get regPoint(): Point + { + return this._regPoint; + } + + public get flipH(): boolean + { + return this._flipH; + } + + public get colorTransform(): number + { + return this._colorTransform; + } + + public get offsetRect(): Rectangle + { + return new Rectangle(-(this._regPoint.x), -(this._regPoint.y), this._rect.width, this._rect.height); + } +} diff --git a/src/app/avatar/cache/ImageData.ts b/src/app/avatar/cache/ImageData.ts index 565bcef..8757694 100644 --- a/src/app/avatar/cache/ImageData.ts +++ b/src/app/avatar/cache/ImageData.ts @@ -1,15 +1,14 @@ -import { Canvas } from 'canvas'; -import { Point, Rectangle } from '../../../core'; +import { Point, Rectangle, Texture } from '../../../core'; export class ImageData { - private _texture: Canvas; + private _texture: Texture; private _rect: Rectangle; private _regPoint: Point; private _flipH: boolean; private _colorTransform: number; - constructor(texture: Canvas, rectangle: Rectangle, _arg_3: Point, flipH: boolean, color: number) + constructor(texture: Texture, rectangle: Rectangle, _arg_3: Point, flipH: boolean, color: number) { this._texture = texture; this._rect = rectangle; @@ -27,7 +26,7 @@ export class ImageData this._colorTransform = null; } - public get texture(): Canvas + public get texture(): Texture { return this._texture; } diff --git a/src/app/avatar/cache/index.ts b/src/app/avatar/cache/index.ts index d8d2e53..d3763e7 100644 --- a/src/app/avatar/cache/index.ts +++ b/src/app/avatar/cache/index.ts @@ -2,4 +2,5 @@ export * from './AvatarImageActionCache'; export * from './AvatarImageBodyPartCache'; export * from './AvatarImageCache'; export * from './AvatarImageDirectionCache'; +export * from './CompleteImageData'; export * from './ImageData'; diff --git a/src/core/asset/AssetManager.ts b/src/core/asset/AssetManager.ts index 35ad4f3..192a7b1 100644 --- a/src/core/asset/AssetManager.ts +++ b/src/core/asset/AssetManager.ts @@ -1,13 +1,12 @@ -import { Canvas } from 'canvas'; import { NitroManager } from '../common'; -import { AdvancedMap, FileUtilities } from '../utils'; +import { AdvancedMap, FileUtilities, Texture } from '../utils'; import { IAssetManager } from './IAssetManager'; import { NitroBundle } from './NitroBundle'; import { GraphicAssetCollection, IGraphicAsset, IGraphicAssetCollection } from './utils'; export class AssetManager extends NitroManager implements IAssetManager { - private _textures: AdvancedMap; + private _textures: AdvancedMap; private _collections: AdvancedMap; constructor() @@ -18,7 +17,7 @@ export class AssetManager extends NitroManager implements IAssetManager this._collections = new AdvancedMap(); } - public getTexture(name: string): Canvas + public getTexture(name: string): Texture { if(!name) return null; @@ -29,7 +28,7 @@ export class AssetManager extends NitroManager implements IAssetManager return existing; } - public setTexture(name: string, texture: Canvas): void + public setTexture(name: string, texture: Texture): void { if(!name || !texture) return; diff --git a/src/core/asset/IAssetManager.ts b/src/core/asset/IAssetManager.ts index 630d5d4..86cafce 100644 --- a/src/core/asset/IAssetManager.ts +++ b/src/core/asset/IAssetManager.ts @@ -1,12 +1,12 @@ -import { Canvas } from 'canvas'; import { INitroManager } from '../common'; +import { AdvancedMap, Texture } from '../utils'; import { NitroBundle } from './NitroBundle'; import { IGraphicAsset, IGraphicAssetCollection } from './utils'; export interface IAssetManager extends INitroManager { - getTexture(name: string): Canvas; - setTexture(name: string, texture: Canvas): void; + getTexture(name: string): Texture; + setTexture(name: string, texture: Texture): void; getAsset(name: string): IGraphicAsset; getCollection(name: string): IGraphicAssetCollection; createCollectionFromNitroBundle(bundle: NitroBundle): IGraphicAssetCollection; diff --git a/src/core/asset/utils/GraphicAsset.ts b/src/core/asset/utils/GraphicAsset.ts index 59b926e..32c7d61 100644 --- a/src/core/asset/utils/GraphicAsset.ts +++ b/src/core/asset/utils/GraphicAsset.ts @@ -1,5 +1,4 @@ -import { Canvas } from 'canvas'; -import { Rectangle } from '../../utils'; +import { Rectangle, Texture } from '../../utils'; import { IGraphicAsset } from './IGraphicAsset'; export class GraphicAsset implements IGraphicAsset @@ -8,7 +7,7 @@ export class GraphicAsset implements IGraphicAsset private _name: string; private _source: string; - private _texture: Canvas; + private _texture: Texture; private _usesPalette: boolean; private _x: number; private _y: number; @@ -19,7 +18,7 @@ export class GraphicAsset implements IGraphicAsset private _rectangle: Rectangle; private _initialized: boolean; - public static createAsset(name: string, source: string, texture: Canvas, x: number, y: number, flipH: boolean = false, flipV: boolean = false, usesPalette: boolean = false): GraphicAsset + public static createAsset(name: string, source: string, texture: Texture, x: number, y: number, flipH: boolean = false, flipV: boolean = false, usesPalette: boolean = false): GraphicAsset { const graphicAsset = (GraphicAsset.GRAPHIC_POOL.length ? GraphicAsset.GRAPHIC_POOL.pop() : new GraphicAsset()); @@ -74,7 +73,7 @@ export class GraphicAsset implements IGraphicAsset return this._source; } - public get texture(): Canvas + public get texture(): Texture { return this._texture; } diff --git a/src/core/asset/utils/GraphicAssetCollection.ts b/src/core/asset/utils/GraphicAssetCollection.ts index 7525719..04f6bdc 100644 --- a/src/core/asset/utils/GraphicAssetCollection.ts +++ b/src/core/asset/utils/GraphicAssetCollection.ts @@ -1,5 +1,5 @@ -import { Canvas, createCanvas, Image } from 'canvas'; -import { AdvancedMap, Rectangle } from '../../utils'; +import { Image } from 'canvas'; +import { AdvancedMap, BaseTexture, Rectangle, Texture } from '../../utils'; import { IAsset, IAssetData, IAssetPalette } from '../interfaces'; import { GraphicAsset } from './GraphicAsset'; import { GraphicAssetPalette } from './GraphicAssetPalette'; @@ -12,12 +12,12 @@ export class GraphicAssetCollection implements IGraphicAssetCollection private _name: string; private _data: IAssetData; - private _textures: AdvancedMap; + private _textures: AdvancedMap; private _assets: AdvancedMap; private _palettes: AdvancedMap; private _paletteAssetNames: string[]; - constructor(data: IAssetData, baseTexture: Image) + constructor(data: IAssetData, image: Image) { if(!data) throw new Error('invalid_collection'); @@ -28,7 +28,12 @@ export class GraphicAssetCollection implements IGraphicAssetCollection this._palettes = new AdvancedMap(); this._paletteAssetNames = []; - if(baseTexture) this.processBaseTexture(baseTexture); + if(image) + { + const baseTexture = new BaseTexture(image); + + this.processBaseTexture(baseTexture); + } this.define(data); } @@ -57,7 +62,7 @@ export class GraphicAssetCollection implements IGraphicAssetCollection } } - private processBaseTexture(baseTexture: Image): void + private processBaseTexture(baseTexture: BaseTexture): void { const frames = this._data.spritesheet.frames; @@ -107,58 +112,9 @@ export class GraphicAssetCollection implements IGraphicAssetCollection ); } - const width = frame.width; - const height = frame.height; + const texture = new Texture(baseTexture, frame, orig, trim); - let dx = 0; - let dy = 0; - - if(trim) - { - dx = (trim.width / 2) + trim.x - (0 * orig.width); - dy = (trim.height / 2) + trim.y - (0 * orig.height); - } - else - { - dx = (0.5 - 0) * orig.width; - dy = (0.5 - 0) * orig.height; - } - - dx -= width / 2; - dy -= height / 2; - - const canvas = createCanvas(sourceSize.w, sourceSize.h); - const ctx = canvas.getContext('2d'); - - ctx.drawImage( - baseTexture, - frame.x * resolution, - frame.y * resolution, - Math.floor(width * resolution), - Math.floor(height * resolution), - Math.floor(dx * resolution), - Math.floor(dy * resolution), - Math.floor(width * resolution), - Math.floor(height * resolution) - ); - - if(name.indexOf('h_std_hr_4_2_0') >= 0) - { - //console.log(spritesheetFrame); - console.log('h_std_hr_4_2_0'); - console.log(canvas.toDataURL()); - console.log(); - } - - if(name.indexOf('h_std_hrb_4_2_0') >= 0) - { - //console.log(frameData, source); - console.log('h_std_hrb_4_2_0'); - console.log(canvas.toDataURL()); - console.log(); - } - - this._textures.add(name, canvas); + this._textures.add(name, texture); } } @@ -242,7 +198,7 @@ export class GraphicAssetCollection implements IGraphicAssetCollection } } - private createAsset(name: string, source: string, texture: Canvas, flipH: boolean, flipV: boolean, x: number, y: number, usesPalette: boolean): boolean + private createAsset(name: string, source: string, texture: Texture, flipH: boolean, flipV: boolean, x: number, y: number, usesPalette: boolean): boolean { if(this._assets.getValue(name)) return false; @@ -253,7 +209,7 @@ export class GraphicAssetCollection implements IGraphicAssetCollection return true; } - private replaceAsset(name: string, source: string, texture: Canvas, flipH: boolean, flipV: boolean, x: number, y: number, usesPalette: boolean): boolean + private replaceAsset(name: string, source: string, texture: Texture, flipH: boolean, flipV: boolean, x: number, y: number, usesPalette: boolean): boolean { const existing = this._assets.getValue(name); @@ -350,7 +306,7 @@ export class GraphicAssetCollection implements IGraphicAssetCollection existing.recycle(); } - public getLibraryAsset(name: string): Canvas + public getLibraryAsset(name: string): Texture { if(!name) return null; @@ -386,7 +342,7 @@ export class GraphicAssetCollection implements IGraphicAssetCollection return this._data; } - public get textures(): AdvancedMap + public get textures(): AdvancedMap { return this._textures; } diff --git a/src/core/asset/utils/GraphicAssetPalette.ts b/src/core/asset/utils/GraphicAssetPalette.ts index 5af18e9..1b0a8d8 100644 --- a/src/core/asset/utils/GraphicAssetPalette.ts +++ b/src/core/asset/utils/GraphicAssetPalette.ts @@ -1,4 +1,4 @@ -import { Canvas } from 'canvas'; +import { Texture } from '../../utils'; export class GraphicAssetPalette { @@ -21,7 +21,7 @@ export class GraphicAssetPalette } - public applyPalette(texture: Canvas): Canvas + public applyPalette(texture: Texture): Texture { return null; diff --git a/src/core/asset/utils/IGraphicAsset.ts b/src/core/asset/utils/IGraphicAsset.ts index f97135c..e9aadec 100644 --- a/src/core/asset/utils/IGraphicAsset.ts +++ b/src/core/asset/utils/IGraphicAsset.ts @@ -1,11 +1,10 @@ -import { Canvas } from 'canvas'; -import { Rectangle } from '../../utils'; +import { Rectangle, Texture } from '../../utils'; export interface IGraphicAsset { name: string; source: string; - texture: Canvas; + texture: Texture; usesPalette: boolean; x: number; y: number; diff --git a/src/core/utils/BaseTexture.ts b/src/core/utils/BaseTexture.ts new file mode 100644 index 0000000..da7e2fb --- /dev/null +++ b/src/core/utils/BaseTexture.ts @@ -0,0 +1,28 @@ +import { Image } from 'canvas'; + +export class BaseTexture +{ + private _image: Image; + private _resolution: number; + + constructor(image: Image) + { + this._image = image; + this._resolution = 1; + } + + public getDrawableSource(): Image + { + return this._image; + } + + public get image(): Image + { + return this._image; + } + + public get resolution(): number + { + return this._resolution; + } +} diff --git a/src/core/utils/CanvasUtilities.ts b/src/core/utils/CanvasUtilities.ts new file mode 100644 index 0000000..930f869 --- /dev/null +++ b/src/core/utils/CanvasUtilities.ts @@ -0,0 +1,102 @@ +import { Canvas, createCanvas } from 'canvas'; +import { Texture } from './Texture'; + +export class CanvasUtilities +{ + public static cropTransparentPixels(canvas: Canvas): Canvas + { + const bounds = { + top: null, + left: null, + right: null, + bottom: null + }; + + const ctx = canvas.getContext('2d'); + const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height); + const length = pixels.data.length; + + for(let i = 0; i < length; i += 4) + { + if(pixels.data[i+3] !== 0) + { + const x = (i / 4) % canvas.width; + const y = ~~((i / 4) / canvas.width); + + if(bounds.top === null) bounds.top = y; + + if(bounds.left === null) bounds.left = x; + else if (x < bounds.left) bounds.left = x; + + if(bounds.right === null) bounds.right = x; + else if(bounds.right < x) bounds.right = x; + + if(bounds.bottom === null) bounds.bottom = y; + else if (bounds.bottom < y) bounds.bottom = y; + } + } + + const trimHeight = (bounds.bottom - bounds.top); + const trimWidth = (bounds.right - bounds.left); + const trimmed = ctx.getImageData(bounds.left, bounds.top, trimWidth, trimHeight); + + canvas.width = trimWidth; + canvas.height = trimHeight; + ctx.putImageData(trimmed, 0, 0); + + return canvas; + } + + public static tintWithMultiply(texture: Texture, color: number): Canvas + { + const crop = texture.frame.clone(); + const resolution = texture.baseTexture.resolution; + + const canvas = createCanvas(Math.ceil(crop.width), Math.ceil(crop.height)); + const ctx = canvas.getContext('2d'); + + crop.x *= resolution; + crop.y *= resolution; + crop.width *= resolution; + crop.height *= resolution; + + ctx.save(); + ctx.fillStyle = `#${(`00000${(color | 0).toString(16)}`).substr(-6)}`; + + ctx.fillRect(0, 0, crop.width, crop.height); + + ctx.globalCompositeOperation = 'multiply'; + + const source = texture.baseTexture.getDrawableSource(); + + ctx.drawImage( + source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + ctx.globalCompositeOperation = 'destination-atop'; + + ctx.drawImage( + source, + crop.x, + crop.y, + crop.width, + crop.height, + 0, + 0, + crop.width, + crop.height + ); + + ctx.restore(); + + return canvas; + } +} diff --git a/src/core/utils/Texture.ts b/src/core/utils/Texture.ts new file mode 100644 index 0000000..817611c --- /dev/null +++ b/src/core/utils/Texture.ts @@ -0,0 +1,160 @@ +import { Canvas, createCanvas } from 'canvas'; +import { BaseTexture } from './BaseTexture'; +import { Rectangle } from './Rectangle'; + +export class Texture +{ + private _baseTexture: BaseTexture; + private _frame: Rectangle; + private _orig: Rectangle; + private _trim: Rectangle; + private _drawableCanvas: Canvas; + + constructor(baseTexture: BaseTexture, frame: Rectangle, orig: Rectangle, trim: Rectangle) + { + this._baseTexture = baseTexture; + this._frame = frame; + this._orig = orig; + this._trim = trim; + + this.buildTexture(); + } + + private buildTexture(): void + { + const width = this._frame.width; + const height = this._frame.height; + + let dx = 0; + let dy = 0; + + if(this._trim) + { + dx = (this._trim.width / 2) + this._trim.x - (0 * this._orig.width); + dy = (this._trim.height / 2) + this._trim.y - (0 * this._orig.height); + } + else + { + dx = (0.5 - 0) * this._orig.width; + dy = (0.5 - 0) * this._orig.height; + } + + dx -= width / 2; + dy -= height / 2; + + const canvas = createCanvas(this._orig.width, this._orig.height); + const ctx = canvas.getContext('2d'); + + ctx.drawImage( + this._baseTexture.image, + this._frame.x * this._baseTexture.resolution, + this._frame.y * this._baseTexture.resolution, + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution), + Math.floor(dx * this._baseTexture.resolution), + Math.floor(dy * this._baseTexture.resolution), + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution) + ); + + this._drawableCanvas = canvas; + } + + public getTintedWithMultiply(color: number): Canvas + { + const width = this._frame.width; + const height = this._frame.height; + + let dx = 0; + let dy = 0; + + if(this._trim) + { + dx = (this._trim.width / 2) + this._trim.x - (0 * this._orig.width); + dy = (this._trim.height / 2) + this._trim.y - (0 * this._orig.height); + } + else + { + dx = (0.5 - 0) * this._orig.width; + dy = (0.5 - 0) * this._orig.height; + } + + dx -= width / 2; + dy -= height / 2; + + const canvas = createCanvas(this._orig.width, this._orig.height); + const ctx = canvas.getContext('2d'); + + ctx.save(); + ctx.fillStyle = `#${(`00000${(color | 0).toString(16)}`).substr(-6)}`; + + ctx.fillRect(0, 0, this._orig.width, this._orig.height); + + ctx.globalCompositeOperation = 'multiply'; + + ctx.drawImage( + this._baseTexture.image, + this._frame.x * this._baseTexture.resolution, + this._frame.y * this._baseTexture.resolution, + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution), + Math.floor(dx * this._baseTexture.resolution), + Math.floor(dy * this._baseTexture.resolution), + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution) + ); + + ctx.globalCompositeOperation = 'destination-atop'; + + ctx.drawImage( + this._baseTexture.image, + this._frame.x * this._baseTexture.resolution, + this._frame.y * this._baseTexture.resolution, + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution), + Math.floor(dx * this._baseTexture.resolution), + Math.floor(dy * this._baseTexture.resolution), + Math.floor(width * this._baseTexture.resolution), + Math.floor(height * this._baseTexture.resolution) + ); + + ctx.restore(); + + return canvas; + } + + public get baseTexture(): BaseTexture + { + return this._baseTexture; + } + + public get frame(): Rectangle + { + return this._frame; + } + + public get orig(): Rectangle + { + return this._orig; + } + + public get trim(): Rectangle + { + return this._trim; + } + + public get drawableCanvas(): Canvas + { + return this._drawableCanvas; + } + + public get width(): number + { + return this.drawableCanvas.width; + } + + public get height(): number + { + return this._drawableCanvas.height; + } +} diff --git a/src/core/utils/index.ts b/src/core/utils/index.ts index 270ad8d..320b361 100644 --- a/src/core/utils/index.ts +++ b/src/core/utils/index.ts @@ -1,6 +1,9 @@ export * from './AdvancedMap'; +export * from './BaseTexture'; export * from './BinaryReader'; +export * from './CanvasUtilities'; export * from './File'; export * from './FileUtilities'; export * from './Point'; export * from './Rectangle'; +export * from './Texture';