nitro-imager/src/app/router/habbo-imaging/handlers/HabboImagingRouterGet.ts

237 lines
7.8 KiB
TypeScript

import { Canvas, createCanvas } from 'canvas';
import { Request, Response } from 'express';
import { createWriteStream, writeFile, WriteStream } from 'fs';
import * as GIFEncoder from 'gifencoder';
import { File, FileUtilities, Point } from '../../../../core';
import { Application } from '../../../Application';
import { AvatarScaleType, IAvatarImage } from '../../../avatar';
import { BuildFigureOptionsRequest, BuildFigureOptionsStringRequest, ProcessActionRequest, ProcessDanceRequest, ProcessDirectionRequest, ProcessEffectRequest, ProcessGestureRequest, RequestQuery } from './utils';
export const HabboImagingRouterGet = async (request: Request<any, any, any, RequestQuery>, response: Response) =>
{
const query = request.query;
try
{
const buildOptions = BuildFigureOptionsRequest(query);
const saveDirectory = (process.env.AVATAR_SAVE_PATH as string);
const directory = FileUtilities.getDirectory(saveDirectory);
const avatarString = BuildFigureOptionsStringRequest(buildOptions);
const saveFile = new File(`${ directory.path }/${ avatarString }.${ buildOptions.imageFormat }`);
if(saveFile.exists())
{
const buffer = await FileUtilities.readFileAsBuffer(saveFile.path);
if(buffer)
{
response
.writeHead(200, {
'Content-Type': ((buildOptions.imageFormat === 'gif') ? 'image/gif' : 'image/png')
})
.end(buffer);
}
return;
}
if(buildOptions.effect > 0)
{
if(!Application.instance.avatar.effectManager.isAvatarEffectReady(buildOptions.effect))
{
await Application.instance.avatar.effectManager.downloadAvatarEffect(buildOptions.effect);
}
}
const avatar = await Application.instance.avatar.createAvatarImage(buildOptions.figure, AvatarScaleType.LARGE, 'M');
const avatarCanvas = Application.instance.avatar.structure.getCanvas(avatar.getScale(), avatar.mainAction.definition.geometryType);
ProcessDirectionRequest(query, avatar);
avatar.initActionAppends();
ProcessActionRequest(query, avatar);
ProcessGestureRequest(query, avatar);
ProcessDanceRequest(query, avatar);
ProcessEffectRequest(query, avatar);
avatar.endActionAppends();
const bgColor = 376510773; // magenta
const tempCanvas = createCanvas((avatarCanvas.width * buildOptions.size), (avatarCanvas.height * buildOptions.size));
const tempCtx = tempCanvas.getContext('2d');
let encoder: GIFEncoder = null;
let stream: WriteStream = null;
if(buildOptions.imageFormat === 'gif')
{
encoder = new GIFEncoder(tempCanvas.width, tempCanvas.height);
stream = encoder.createReadStream().pipe(createWriteStream(saveFile.path));
encoder.setTransparent(bgColor);
encoder.start();
encoder.setRepeat(0);
encoder.setDelay(1);
encoder.setQuality(10);
}
let totalFrames = 0;
if(buildOptions.imageFormat !== 'gif')
{
if(buildOptions.frameNumber > 0) avatar.updateAnimationByFrames(buildOptions.frameNumber);
totalFrames = 1;
}
else
{
totalFrames = ((avatar.getTotalFrameCount() * 2) || 1);
}
for(let i = 0; i < totalFrames; i++)
{
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
if(totalFrames && (i > 0)) avatar.updateAnimationByFrames(1);
const canvas = await avatar.getImage(buildOptions.setType, 0, false, buildOptions.size);
const avatarOffset = new Point();
const canvasOffset = new Point();
canvasOffset.x = ((tempCanvas.width - canvas.width) / 2);
canvasOffset.y = ((tempCanvas.height - canvas.height) / 2);
for(const sprite of avatar.getSprites())
{
if(sprite.id === 'avatar')
{
const layerData = avatar.getLayerData(sprite);
avatarOffset.x = sprite.getDirectionOffsetX(buildOptions.direction);
avatarOffset.y = sprite.getDirectionOffsetY(buildOptions.direction);
if(layerData)
{
avatarOffset.x += layerData.dx;
avatarOffset.y += layerData.dy;
}
}
}
const avatarSize = 64;
const sizeOffset = new Point(((canvas.width - avatarSize) / 2), (canvas.height - (avatarSize / 4)));
ProcessAvatarSprites(tempCanvas, avatar, avatarOffset, canvasOffset.add(sizeOffset), false);
tempCtx.drawImage(canvas, avatarOffset.x, avatarOffset.y, canvas.width, canvas.height);
ProcessAvatarSprites(tempCanvas, avatar, avatarOffset, canvasOffset.add(sizeOffset), true);
if(encoder)
{
encoder.addFrame(tempCtx);
}
else
{
const buffer = tempCanvas.toBuffer();
response
.writeHead(200, {
'Content-Type': 'image/png'
})
.end(buffer);
writeFile(saveFile.path, buffer, () => {});
return;
}
}
if(encoder) encoder.finish();
if(stream)
{
await new Promise((resolve, reject) =>
{
stream.on('finish', resolve);
stream.on('error', reject);
});
}
const buffer = await FileUtilities.readFileAsBuffer(saveFile.path);
response
.writeHead(200, {
'Content-Type': 'image/gif'
})
.end(buffer);
}
catch(err)
{
Application.instance.logger.error(err.message);
response
.writeHead(500)
.end();
}
}
function ProcessAvatarSprites(canvas: Canvas, avatar: IAvatarImage, avatarOffset: Point, canvasOffset: Point, frontSprites: boolean = true)
{
const ctx = canvas.getContext('2d');
for(const sprite of avatar.getSprites())
{
if(sprite.id === 'avatar') continue;
const layerData = avatar.getLayerData(sprite);
let offsetX = sprite.getDirectionOffsetX(avatar.getDirection());
let offsetY = sprite.getDirectionOffsetY(avatar.getDirection());
let offsetZ = sprite.getDirectionOffsetZ(avatar.getDirection());
let direction = 0;
let frame = 0;
if(!frontSprites)
{
if(offsetZ >= 0) continue;
}
else if(offsetZ < 0) continue;
if(sprite.hasDirections) direction = avatar.getDirection();
if(layerData)
{
frame = layerData.animationFrame;
offsetX = (offsetX + layerData.dx);
offsetY = (offsetY + layerData.dy);
direction = (direction + layerData.dd);
}
if(direction < 0) direction = (direction + 8);
if(direction > 7) direction = (direction - 8);
const assetName = ((((((avatar.getScale() + "_") + sprite.member) + "_") + direction) + "_") + frame);
const asset = avatar.getAsset(assetName);
if(!asset) continue;
const texture = asset.texture;
let x = ((canvasOffset.x - (1 * asset.offsetX)) + offsetX);
let y = ((canvasOffset.y - (1 * asset.offsetY)) + offsetY);
ctx.save();
if(sprite.ink === 33) ctx.globalCompositeOperation = 'lighter';
ctx.transform(1, 0, 0, 1, (x - avatarOffset.x), (y - avatarOffset.y));
ctx.drawImage(texture.drawableCanvas, 0, 0, texture.width, texture.height);
ctx.restore();
}
}