mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-04-06 17:31:09 +08:00
parent
03ab1fc2d1
commit
8a46f2a2dc
@ -81,7 +81,7 @@ export class Codec extends EventSystem {
|
||||
let opts_in_project = Project.export_options[codec.id];
|
||||
|
||||
for (let form_id in this.export_options) {
|
||||
if (!Condition(this.export_options[form_id].condition)) continue;
|
||||
// if (!Condition(this.export_options[form_id].condition)) continue;
|
||||
form[form_id] = {};
|
||||
for (let key in this.export_options[form_id]) {
|
||||
form[form_id][key] = this.export_options[form_id][key];
|
||||
|
@ -92,22 +92,135 @@ let codec = new Codec('image', {
|
||||
png: 'PNG',
|
||||
jpeg: 'JPEG',
|
||||
webp: 'WebP',
|
||||
gif: 'GIF',
|
||||
}},
|
||||
quality: {type: 'range', label: 'codec.image.quality', value: 1, min: 0, max: 1, step: 0.05, editable_range_label: true, condition: (result) => result?.format != 'png'}
|
||||
alpha_channel: {type: 'checkbox', label: 'codec.image.alpha_channel', condition: (result) => result?.format == 'gif', value: true},
|
||||
animation_fps: {type: 'number', label: 'codec.image.animation_fps', condition: (result) => result?.format == 'gif', value: settings.texture_fps.value},
|
||||
quality: {type: 'range', label: 'codec.image.quality', value: 1, min: 0, max: 1, step: 0.05, editable_range_label: true, condition: (result) => result && ['jpeg', 'webp'].includes(result.format)}
|
||||
},
|
||||
compile(options) {
|
||||
async compile(options) {
|
||||
options = Object.assign(this.getExportOptions(), options);
|
||||
let texture = Texture.getDefault();
|
||||
if (!texture) return;
|
||||
|
||||
let encoding = 'image/'+(options.format??'png');
|
||||
let data_url = texture.canvas.toDataURL(encoding, options.quality);
|
||||
return data_url;
|
||||
|
||||
if (options.format == 'gif') {
|
||||
let gif = GIFEnc.GIFEncoder();
|
||||
let has_transparency = options.alpha_channel ?? true;
|
||||
let i = 0;
|
||||
let format = 'rgb565';
|
||||
let prio_color_accuracy = false;
|
||||
function quantize(data) {
|
||||
let palette = has_transparency ? [[0, 0, 0]] : [];
|
||||
let counter = has_transparency ? [100] : [];
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
if (data[i+3] < 127) {
|
||||
continue;
|
||||
}
|
||||
let r = data[i];
|
||||
let g = data[i+1];
|
||||
let b = data[i+2];
|
||||
let match = palette.findIndex((color, i) => color[0] == r && color[1] == g && color[2] == b && (i != 0 || !has_transparency));
|
||||
if (match == -1) {
|
||||
palette.push([r, g, b])
|
||||
counter.push(1)
|
||||
} else {
|
||||
counter[match] += 1;
|
||||
}
|
||||
if (!prio_color_accuracy && palette.length > 256) break;
|
||||
}
|
||||
let threshold = 4;
|
||||
while (palette.length > 256 && prio_color_accuracy) {
|
||||
counter.forEachReverse((count, index) => {
|
||||
if (index == 0) return;
|
||||
if (count < threshold) {
|
||||
palette.splice(index, 1);
|
||||
counter.splice(index, 1);
|
||||
}
|
||||
});
|
||||
threshold *= 1.5;
|
||||
if (threshold > 50) break;
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
function applyPalette(data, palette) {
|
||||
let array = new Uint8Array(data.length / 4);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (data[i*4+3] < 127) {
|
||||
continue;
|
||||
}
|
||||
let r = data[i*4];
|
||||
let g = data[i*4+1];
|
||||
let b = data[i*4+2];
|
||||
let match = palette.findIndex((color, i) => color[0] == r && color[1] == g && color[2] == b && (i != 0 || !has_transparency));
|
||||
if (match == -1 && prio_color_accuracy) {
|
||||
let closest = palette.filter((color, i) => Math.epsilon(color[0], r, 6) && Math.epsilon(color[1], g, 6) && Math.epsilon(color[2], b, 6) && (i != 0 || !has_transparency));
|
||||
if (!closest.length) {
|
||||
closest = palette.filter((color, i) => Math.epsilon(color[0], r, 24) && Math.epsilon(color[1], g, 24) && Math.epsilon(color[2], b, 128) && (i != 0 || !has_transparency));
|
||||
}
|
||||
if (!closest.length) {
|
||||
closest = palette.filter((color, i) => Math.epsilon(color[0], r, 24) && Math.epsilon(color[1], g, 24) && Math.epsilon(color[2], b, 128) && (i != 0 || !has_transparency));
|
||||
}
|
||||
if (!closest.length) {
|
||||
closest = palette.filter((color, i) => Math.epsilon(color[0], r, 64) && Math.epsilon(color[1], g, 64) && Math.epsilon(color[2], b, 128) && (i != 0 || !has_transparency));
|
||||
}
|
||||
if (!closest.length) {
|
||||
closest = palette.slice();
|
||||
}
|
||||
closest.sort((color_a, color_b) => {
|
||||
let diff_a = Math.pow(color_a[0] + r, 2) + Math.pow(color_a[1] + g, 2) + Math.pow(color_a[2] + b, 2);
|
||||
let diff_b = Math.pow(color_b[0] + r, 2) + Math.pow(color_b[1] + g, 2) + Math.pow(color_b[2] + b, 2);
|
||||
return diff_a - diff_b;
|
||||
})
|
||||
if (closest[0]) {
|
||||
match = palette.indexOf(closest[0]);
|
||||
}
|
||||
}
|
||||
if (match != -1) array[i] = match;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
let width = texture.width;
|
||||
let height = texture.display_height;
|
||||
let interval = 1000 / options.animation_fps;
|
||||
let {ctx} = texture;
|
||||
for (let frame = 0; frame < texture.frameCount; frame++) {
|
||||
let data = ctx.getImageData(0, frame * height, width, height).data;
|
||||
let palette = quantize(data, 256, {format, oneBitAlpha: true, clearAlphaThreshold: 127});
|
||||
let index;
|
||||
if (palette.length > 256) {
|
||||
// Built-in methods
|
||||
palette = GIFEnc.quantize(data, 256, {format, oneBitAlpha: true, clearAlphaThreshold: 127});
|
||||
index = GIFEnc.applyPalette(data, palette, format);
|
||||
} else {
|
||||
// Direct flicker-free color mapping
|
||||
index = applyPalette(data, palette, format);
|
||||
}
|
||||
gif.writeFrame(index, width, height, { palette, delay: interval, transparent: has_transparency });
|
||||
i++;
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
gif.finish();
|
||||
|
||||
let buffer = gif.bytesView();
|
||||
let blob = new Blob([buffer], {type: 'image/gif'});
|
||||
var reader = new FileReader();
|
||||
return await new Promise((resolve, reject) => {
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
})
|
||||
|
||||
} else {
|
||||
let encoding = 'image/'+(options.format??'png');
|
||||
let data_url = texture.canvas.toDataURL(encoding, options.quality);
|
||||
return data_url;
|
||||
}
|
||||
},
|
||||
async export() {
|
||||
let options = await this.promptExportOptions();
|
||||
if (options === null) return;
|
||||
let content = this.compile();
|
||||
let content = await this.compile();
|
||||
Blockbench.export({
|
||||
resource_id: 'image',
|
||||
type: 'Image',
|
||||
|
@ -2332,6 +2332,8 @@
|
||||
"codec.common.embed_textures": "Embed Textures",
|
||||
"codec.common.format": "Format",
|
||||
"codec.image.quality": "Quality",
|
||||
"codec.image.alpha_channel": "Alpha Channel",
|
||||
"codec.image.animation_fps": "Animation FPS",
|
||||
|
||||
"preview.center_camera": "Center Camera",
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user