2019-06-19 18:45:36 +02:00
|
|
|
package arm.io;
|
|
|
|
|
|
2019-06-20 11:23:01 +02:00
|
|
|
import haxe.io.Bytes;
|
|
|
|
|
import haxe.io.BytesOutput;
|
|
|
|
|
import kha.Image;
|
2019-07-17 12:40:24 +02:00
|
|
|
import arm.format.ExrWriter;
|
|
|
|
|
import arm.format.JpgWriter;
|
|
|
|
|
import arm.format.PngWriter;
|
|
|
|
|
import arm.format.PngTools;
|
2019-06-19 18:45:36 +02:00
|
|
|
import arm.ui.UITrait;
|
2019-06-23 15:44:23 +02:00
|
|
|
import arm.ui.UIFiles;
|
2019-11-20 16:36:08 +01:00
|
|
|
import arm.ui.BoxExport;
|
2019-12-08 16:59:08 +01:00
|
|
|
import arm.Tool;
|
2019-06-20 12:30:31 +02:00
|
|
|
using StringTools;
|
2019-06-19 18:45:36 +02:00
|
|
|
|
2019-11-20 16:36:08 +01:00
|
|
|
class ExportTexture {
|
2019-06-19 18:45:36 +02:00
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
public static function run(path: String) {
|
2019-07-11 16:10:48 +02:00
|
|
|
#if arm_debug
|
|
|
|
|
var timer = iron.system.Time.realTime();
|
|
|
|
|
#end
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
var udimTiles: Array<String> = [];
|
2019-11-20 23:44:41 +01:00
|
|
|
for (l in Project.layers) {
|
|
|
|
|
if (l.objectMask > 0) {
|
|
|
|
|
var name = Project.paintObjects[l.objectMask - 1].name;
|
|
|
|
|
if (name.substr(name.length - 5, 2) == ".1") { // tile.1001
|
|
|
|
|
udimTiles.push(name.substr(name.length - 5));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (udimTiles.length > 0 && UITrait.inst.layersExport == 0) {
|
|
|
|
|
for (udimTile in udimTiles) runLayers(path, udimTile);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
runLayers(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if arm_debug
|
|
|
|
|
trace("Textures exported in " + (iron.system.Time.realTime() - timer));
|
|
|
|
|
#end
|
|
|
|
|
|
2019-11-23 22:42:09 +01:00
|
|
|
Log.info("Textures exported.");
|
2019-11-20 23:44:41 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
static function runLayers(path: String, udimTile = "") {
|
2019-06-19 18:45:36 +02:00
|
|
|
var textureSize = Config.getTextureRes();
|
|
|
|
|
var formatQuality = UITrait.inst.formatQuality;
|
2019-06-23 15:44:23 +02:00
|
|
|
var f = UIFiles.filename;
|
2019-06-19 18:45:36 +02:00
|
|
|
if (f == "") f = "untitled";
|
|
|
|
|
var formatType = UITrait.inst.formatType;
|
2019-12-11 21:30:29 +01:00
|
|
|
var bits = App.bitsHandle.position == Bits8 ? 8 : 16;
|
2019-12-08 16:59:08 +01:00
|
|
|
var ext = bits == 16 ? ".exr" : formatType == FormatPng ? ".png" : ".jpg";
|
2019-06-20 12:30:31 +02:00
|
|
|
if (f.endsWith(ext)) f = f.substr(0, f.length - 4);
|
2019-11-20 23:44:41 +01:00
|
|
|
ext = udimTile + ext;
|
2019-12-09 00:28:10 +01:00
|
|
|
var texpaint: Image = null;
|
|
|
|
|
var texpaint_nor: Image = null;
|
|
|
|
|
var texpaint_pack: Image = null;
|
2019-06-23 14:50:41 +02:00
|
|
|
var layers = Project.layers;
|
2019-06-19 18:45:36 +02:00
|
|
|
|
|
|
|
|
// Export visible layers
|
|
|
|
|
if (UITrait.inst.layersExport == 0 && layers.length > 1) {
|
|
|
|
|
Layers.makeTempImg();
|
|
|
|
|
Layers.makeExportImg();
|
|
|
|
|
if (Layers.pipe == null) Layers.makePipe();
|
|
|
|
|
if (iron.data.ConstData.screenAlignedVB == null) iron.data.ConstData.createScreenAlignedData();
|
|
|
|
|
|
|
|
|
|
// Duplicate base layer
|
2019-11-20 23:44:41 +01:00
|
|
|
if (layers[0].visible && udimTile == "") {
|
2019-06-19 18:45:36 +02:00
|
|
|
Layers.expa.g2.begin(false);
|
|
|
|
|
Layers.expa.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.expa.g2.drawImage(layers[0].texpaint, 0, 0);
|
|
|
|
|
Layers.expa.g2.end();
|
|
|
|
|
Layers.expb.g2.begin(false);
|
|
|
|
|
Layers.expb.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.expb.g2.drawImage(layers[0].texpaint_nor, 0, 0);
|
|
|
|
|
Layers.expb.g2.end();
|
|
|
|
|
Layers.expc.g2.begin(false);
|
|
|
|
|
Layers.expc.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.expc.g2.drawImage(layers[0].texpaint_pack, 0, 0);
|
|
|
|
|
Layers.expc.g2.end();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Layers.expa.g4.begin();
|
|
|
|
|
Layers.expa.g4.clear(kha.Color.fromFloats(0.0, 0.0, 0.0, 0.0));
|
|
|
|
|
Layers.expa.g4.end();
|
|
|
|
|
Layers.expb.g4.begin();
|
|
|
|
|
Layers.expb.g4.clear(kha.Color.fromFloats(0.5, 0.5, 1.0, 0.0));
|
|
|
|
|
Layers.expb.g4.end();
|
|
|
|
|
Layers.expc.g4.begin();
|
|
|
|
|
Layers.expc.g4.clear(kha.Color.fromFloats(1.0, 0.0, 0.0, 0.0));
|
|
|
|
|
Layers.expc.g4.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i in 1...layers.length) {
|
|
|
|
|
var l1 = layers[i];
|
|
|
|
|
if (!l1.visible) continue;
|
|
|
|
|
|
2019-11-20 23:44:41 +01:00
|
|
|
if (udimTile != "" && l1.objectMask > 0) {
|
|
|
|
|
if (!Project.paintObjects[l1.objectMask - 1].name.endsWith(udimTile)) continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-19 18:45:36 +02:00
|
|
|
// Apply mask
|
|
|
|
|
var hasMask = l1.texpaint_mask != null;
|
|
|
|
|
if (hasMask) {
|
|
|
|
|
Layers.expd.g4.begin();
|
|
|
|
|
Layers.expd.g4.setPipeline(Layers.pipeMask);
|
|
|
|
|
Layers.expd.g4.setTexture(Layers.tex0Mask, l1.texpaint);
|
|
|
|
|
Layers.expd.g4.setTexture(Layers.texaMask, l1.texpaint_mask);
|
|
|
|
|
Layers.expd.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB);
|
|
|
|
|
Layers.expd.g4.setIndexBuffer(iron.data.ConstData.screenAlignedIB);
|
|
|
|
|
Layers.expd.g4.drawIndexedVertices();
|
|
|
|
|
Layers.expd.g4.end();
|
|
|
|
|
}
|
2019-10-06 19:03:59 +02:00
|
|
|
|
2019-06-19 18:45:36 +02:00
|
|
|
// Copy layer0 to temp
|
|
|
|
|
Layers.imga.g2.begin(false);
|
|
|
|
|
Layers.imga.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.imga.g2.drawImage(Layers.expa, 0, 0);
|
|
|
|
|
Layers.imga.g2.end();
|
|
|
|
|
Layers.imgb.g2.begin(false);
|
|
|
|
|
Layers.imgb.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.imgb.g2.drawImage(Layers.expb, 0, 0);
|
|
|
|
|
Layers.imgb.g2.end();
|
|
|
|
|
Layers.imgc.g2.begin(false);
|
|
|
|
|
Layers.imgc.g2.pipeline = Layers.pipeCopy;
|
|
|
|
|
Layers.imgc.g2.drawImage(Layers.expc, 0, 0);
|
|
|
|
|
Layers.imgc.g2.end();
|
2019-10-06 19:03:59 +02:00
|
|
|
|
2019-06-19 18:45:36 +02:00
|
|
|
// Merge into layer0
|
|
|
|
|
Layers.expa.g4.begin([Layers.expb, Layers.expc]);
|
|
|
|
|
Layers.expa.g4.setPipeline(Layers.pipe);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.tex0, hasMask ? Layers.expd : l1.texpaint);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.tex1, l1.texpaint_nor);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.tex2, l1.texpaint_pack);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.texa, Layers.imga);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.texb, Layers.imgb);
|
|
|
|
|
Layers.expa.g4.setTexture(Layers.texc, Layers.imgc);
|
|
|
|
|
Layers.expa.g4.setFloat(Layers.opac, l1.maskOpacity);
|
2019-11-20 23:44:41 +01:00
|
|
|
Layers.expa.g4.setInt(Layers.blending, l1.blending);
|
2019-06-19 18:45:36 +02:00
|
|
|
Layers.expa.g4.setVertexBuffer(iron.data.ConstData.screenAlignedVB);
|
|
|
|
|
Layers.expa.g4.setIndexBuffer(iron.data.ConstData.screenAlignedIB);
|
|
|
|
|
Layers.expa.g4.drawIndexedVertices();
|
|
|
|
|
Layers.expa.g4.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
texpaint = Layers.expa;
|
|
|
|
|
texpaint_nor = Layers.expb;
|
|
|
|
|
texpaint_pack = Layers.expc;
|
|
|
|
|
}
|
2019-12-09 00:28:10 +01:00
|
|
|
else { // Export selected layer
|
2019-06-23 14:50:41 +02:00
|
|
|
var selectedLayer = Context.layer;
|
2019-06-19 18:45:36 +02:00
|
|
|
texpaint = selectedLayer.texpaint;
|
|
|
|
|
texpaint_nor = selectedLayer.texpaint_nor;
|
|
|
|
|
texpaint_pack = selectedLayer.texpaint_pack;
|
|
|
|
|
if (selectedLayer.objectMask > 0) { // Append object mask name
|
2019-06-24 22:35:17 +02:00
|
|
|
f += "_" + Project.paintObjects[selectedLayer.objectMask].name;
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
var pixpaint: Bytes = null;
|
|
|
|
|
var pixpaint_nor: Bytes = null;
|
|
|
|
|
var pixpaint_pack: Bytes = null;
|
2019-11-20 16:36:08 +01:00
|
|
|
var preset = BoxExport.preset;
|
2019-12-09 00:28:10 +01:00
|
|
|
var pix: Bytes = null;
|
2019-06-19 18:45:36 +02:00
|
|
|
|
2019-11-20 16:36:08 +01:00
|
|
|
for (t in preset.textures) {
|
|
|
|
|
for (c in t.channels) {
|
|
|
|
|
if ((c == "base_r" || c == "base_g" || c == "base_b" || c == "opac") && pixpaint == null) pixpaint = texpaint.getPixels();
|
|
|
|
|
else if ((c == "nor_r" || c == "nor_g" || c == "nor_b") && pixpaint_nor == null) pixpaint_nor = texpaint_nor.getPixels();
|
|
|
|
|
else if ((c == "occ" || c == "rough" || c == "metal" || c == "height" || c == "smooth") && pixpaint_pack == null) pixpaint_pack = texpaint_pack.getPixels();
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-20 16:36:08 +01:00
|
|
|
for (t in preset.textures) {
|
|
|
|
|
var c = t.channels;
|
|
|
|
|
var singleChannel = c[0] == c[1] && c[1] == c[2] && c[3] == "1.0";
|
|
|
|
|
if (c[0] == "base_r" && c[1] == "base_g" && c[2] == "base_b" && c[3] == "1.0") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint, 1);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
else if (c[0] == "nor_r" && c[1] == "nor_g" && c[2] == "nor_b" && c[3] == "1.0") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_nor, 1);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
else if (c[0] == "occ" && c[1] == "rough" && c[2] == "metal" && c[3] == "1.0") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_pack, 1);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
else if (singleChannel && c[0] == "occ") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_pack, 2, 0);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
else if (singleChannel && c[0] == "rough") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_pack, 2, 1);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
else if (singleChannel && c[0] == "metal") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_pack, 2, 2);
|
|
|
|
|
}
|
|
|
|
|
else if (singleChannel && c[0] == "height") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint_pack, 2, 3);
|
|
|
|
|
}
|
|
|
|
|
else if (singleChannel && c[0] == "opac") {
|
|
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pixpaint, 2, 3);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2019-11-20 16:36:08 +01:00
|
|
|
if (pix == null) pix = Bytes.alloc(textureSize * textureSize * 4 * Std.int(bits / 8));
|
|
|
|
|
for (i in 0...4) {
|
|
|
|
|
var c = t.channels[i];
|
|
|
|
|
if (c == "base_r") copyChannel(pixpaint, 0, pix, i); // copyChannelGamma
|
|
|
|
|
else if (c == "base_g") copyChannel(pixpaint, 1, pix, i); // copyChannelGamma
|
|
|
|
|
else if (c == "base_b") copyChannel(pixpaint, 2, pix, i); // copyChannelGamma
|
|
|
|
|
else if (c == "height") copyChannel(pixpaint_pack, 4, pix, i);
|
|
|
|
|
else if (c == "metal") copyChannel(pixpaint_pack, 3, pix, i);
|
|
|
|
|
else if (c == "nor_r") copyChannel(pixpaint_nor, 0, pix, i);
|
|
|
|
|
else if (c == "nor_g") copyChannel(pixpaint_nor, 1, pix, i);
|
|
|
|
|
else if (c == "nor_b") copyChannel(pixpaint_nor, 2, pix, i);
|
|
|
|
|
else if (c == "occ") copyChannel(pixpaint_pack, 0, pix, i);
|
|
|
|
|
else if (c == "opac") copyChannel(pixpaint, 3, pix, i);
|
|
|
|
|
else if (c == "rough") copyChannel(pixpaint_pack, 1, pix, i);
|
|
|
|
|
else if (c == "smooth") copyChannelInv(pixpaint_pack, 1, pix, i);
|
|
|
|
|
else if (c == "0.0") setChannel(0, pix, i);
|
|
|
|
|
else if (c == "1.0") setChannel(255, pix, i);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-20 16:36:08 +01:00
|
|
|
writeTexture(path + "/" + f + "_" + t.name + ext, pix);
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
static function writeTexture(file: String, pixels: Bytes, type = 1, off = 0) {
|
2019-11-20 16:36:08 +01:00
|
|
|
var out = new BytesOutput();
|
|
|
|
|
var res = Config.getTextureRes();
|
2019-12-11 21:30:29 +01:00
|
|
|
var bitsHandle = App.bitsHandle.position;
|
2019-12-08 16:59:08 +01:00
|
|
|
var bits = bitsHandle == Bits8 ? 8 : bitsHandle == Bits16 ? 16 : 32;
|
2019-11-20 16:36:08 +01:00
|
|
|
if (bits > 8) { // 16/32bit
|
|
|
|
|
var writer = new ExrWriter(out, res, res, pixels, bits, type, off);
|
|
|
|
|
}
|
2019-12-08 16:59:08 +01:00
|
|
|
else if (UITrait.inst.formatType == FormatPng) {
|
2019-11-20 16:36:08 +01:00
|
|
|
var writer = new PngWriter(out);
|
|
|
|
|
var data =
|
|
|
|
|
type == 1 ?
|
|
|
|
|
PngTools.build32RGB1(res, res, pixels) :
|
|
|
|
|
type == 2 ?
|
|
|
|
|
PngTools.build32RRR1(res, res, pixels, off) :
|
|
|
|
|
PngTools.build32RGBA(res, res, pixels);
|
|
|
|
|
writer.write(data);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
var writer = new JpgWriter(out);
|
|
|
|
|
writer.write({
|
|
|
|
|
width: res,
|
|
|
|
|
height: res,
|
|
|
|
|
quality: UITrait.inst.formatQuality,
|
|
|
|
|
pixels: pixels
|
|
|
|
|
}, type, off);
|
|
|
|
|
}
|
|
|
|
|
Krom.fileSaveBytes(file, out.getBytes().getData());
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
static function copyChannel(from: Bytes, fromChannel: Int, to: Bytes, toChannel: Int) {
|
2019-11-20 16:36:08 +01:00
|
|
|
for (i in 0...Std.int(to.length / 4)) {
|
|
|
|
|
to.set(i * 4 + toChannel, from.get(i * 4 + fromChannel));
|
|
|
|
|
}
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-09-01 09:28:54 +02:00
|
|
|
|
2019-11-20 16:36:08 +01:00
|
|
|
static inline var gamma = 1.0 / 2.2;
|
2019-12-09 00:28:10 +01:00
|
|
|
static function copyChannelGamma(from: Bytes, fromChannel: Int, to: Bytes, toChannel: Int) {
|
2019-11-20 16:36:08 +01:00
|
|
|
for (i in 0...Std.int(to.length / 4)) {
|
|
|
|
|
to.set(i * 4 + toChannel, Std.int(Math.pow(from.get(i * 4 + fromChannel) / 255, gamma) * 255));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
static function copyChannelInv(from: Bytes, fromChannel: Int, to: Bytes, toChannel: Int) {
|
2019-11-20 16:36:08 +01:00
|
|
|
for (i in 0...Std.int(to.length / 4)) {
|
|
|
|
|
to.set(i * 4 + toChannel, 255 - from.get(i * 4 + fromChannel));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 00:28:10 +01:00
|
|
|
static function setChannel(value: Int, to: Bytes, toChannel: Int) {
|
2019-11-20 16:36:08 +01:00
|
|
|
for (i in 0...Std.int(to.length / 4)) {
|
|
|
|
|
to.set(i * 4 + toChannel, value);
|
|
|
|
|
}
|
2019-09-01 09:28:54 +02:00
|
|
|
}
|
2019-06-19 18:45:36 +02:00
|
|
|
}
|
2019-11-19 22:03:34 +01:00
|
|
|
|
|
|
|
|
typedef TExportPreset = {
|
2019-12-09 00:28:10 +01:00
|
|
|
public var textures: Array<TExportPresetTexture>;
|
2019-11-19 22:03:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef TExportPresetTexture = {
|
2019-12-09 00:28:10 +01:00
|
|
|
public var name: String;
|
|
|
|
|
public var channels: Array<String>;
|
2019-11-19 22:03:34 +01:00
|
|
|
}
|