#include "pch.h" #include "abr.h" #include "log.h" #include "asset.h" bool ABR::section_desc() { auto sz = align4(ru32()); auto n1 = ri32(); // some integer auto s = rwstring(); // maybe a string auto null = rkey_or_string(); auto null_val = ri32(); // integer following the null // presets list auto name = rkey_or_string(); // "Brsh" auto list = rstring(4); // "VlLs" if (auto presets = std::dynamic_pointer_cast(instanciate(list))) { presets->read(*this); for (auto const& pr : presets->items) { if (auto desc = std::dynamic_pointer_cast(pr)) m_presets.push_back(desc); } #ifdef _DEBUG auto out = presets->str(0, ""); auto lines = split(out, '\n'); for (const auto& l : lines) { LOG("%s", l.c_str()); } #endif // _DEBUG } snap(); return true; } bool ABR::section_samp() { auto section_size = ru32(); auto start = pos(); auto end = start + section_size; while (pos() < end) { auto samp_size = ru32(); auto uid = rpascal(); //printf("sample brush %s\n", uid.c_str()); skip(4); // unknown bytes usually 00 01 00 00 auto vm = std::make_shared(); if (vm->read(*this)) { auto img_data = vm->image(true, true); if (img_data.data) { // TODO: check if uid already exists in map m_samples[uid] = std::make_shared(img_data); } } } return true; } bool ABR::section_patt() { auto section_size = ru32(); auto start = pos(); auto end = start + section_size; while (pos() < end) { auto patt_length = ru32(); // length of this pattern auto patt_version = ru32(); // = 1 // Bitmap = 0; Grayscale = 1; Indexed = 2; RGB = 3; // CMYK = 4; Multichannel = 7; Duotone = 8; Lab = 9 auto image_mode = ru32(); if (!(image_mode == 1 || image_mode == 3)) { LOG("PATT: skip image mode %d\n", image_mode); skip(patt_length - 8); snap(); continue; } Point point; point.read(*this); auto name = rwstring(); auto uid = rpascal(); // Index color table (256 * 3 RGB values): // only present when image mode is indexed color // read(...); // no worries indexed is skipped anyway auto vm = std::make_shared(); if (vm->read(*this)) { int nc = std::min((int)vm->channels.size(), 3); if (nc != image_mode) { LOG("PATT: image_mode (%d) and number of channels (%ld) not matching\n", image_mode, vm->channels.size()); } auto img_data = vm->image(true, false); if (img_data.data) { // TODO: check if uid already exists in map m_patterns[uid] = std::make_shared(img_data); } } } return true; } std::vector> ABR::compute_brushes(const std::string& path) { std::vector> ret; for (auto const& p : m_presets) { auto samp = p->get("Brsh"); if (samp->class_id != "sampledBrush" && samp->class_id != "computedBrush") { LOG("unsupported brush type %s", samp->class_id.c_str()); continue; } auto b = std::make_shared(); b->m_name = wstr2str(p->value("Nm ")); // default values b->m_tip_color = { 0, 0, 0, 1 }; b->m_tip_flow = .90f; b->m_tip_opacity = 1.f; b->m_tip_wet = p->value("Wtdg"); b->m_tip_noise = p->value("Nose") ? 1.f : 0.f; b->m_tip_aspect = (1.f - samp->value("Rndn") * 0.01) * 0.5f + 0.5f; b->m_tip_size = samp->value("Dmtr"); b->m_tip_spacing = samp->value("Spcn") * 0.01f; float tip_angle = -samp->value("Angl") / 360.f; b->m_tip_angle = tip_angle >= 0.f ? tip_angle : tip_angle + 1.f; // [-180, 180] -> [0, 1] b->m_tip_flipx = samp->value("flipX"); b->m_tip_flipy = samp->value("flipY"); // brush sample if (samp->class_id == "sampledBrush") { auto tip_uid = wstr2str(samp->value("sampledData")); b->m_brush_path = path + "/brushes/" + tip_uid + ".png"; b->m_brush_thumb_path = path + "/brushes/thumbs/" + tip_uid + ".png"; const auto& samp_img = m_samples[tip_uid]; if (samp_img->height > samp_img->width) b->m_tip_scale = { (float)samp_img->width / (float)samp_img->height, 1.f }; else b->m_tip_scale = { 1.f, (float)samp_img->height / (float)samp_img->width }; } else if (samp->class_id == "computedBrush") { if (samp->value("Hrdn") > 50.f) { b->m_brush_path = "data/brushes/Round-Hard.png"; b->m_brush_thumb_path = "data/brushes/thumbs/Round-Hard.png"; } else { b->m_brush_path = "data/brushes/Round-Brush.png"; b->m_brush_thumb_path = "data/brushes/thumbs/Round-Brush.png"; } } // Shape dynamics if (p->value("useTipDynamics")) { // other properties 'brushProjection', 'minimumRoundness', 'tiltScale' if (auto jitter_size = p->get("szVr")) { b->m_jitter_scale = jitter_size->value("jitter") * 0.01f; // TODO: p->value("minimumDiameter") * 0.001f; // minimum size if (jitter_size->value("bVTy") == 2) b->m_tip_size_pressure = true; } if (auto jitter_angle = p->get("angleDynamics")) { auto mode = jitter_angle->value("bVTy"); if (mode == 0) { b->m_jitter_angle = jitter_angle->value("jitter") * 0.01f; } else if (mode == 5) { b->m_jitter_angle = jitter_angle->value("jitter") * 0.01f; b->m_tip_angle_init = true; } else if (mode == 6) { b->m_jitter_angle = jitter_angle->value("jitter") * 0.01f; b->m_tip_angle_follow = true; } } if (auto roundness = p->get("roundnessDynamics")) { b->m_jitter_aspect = roundness->value("jitter") * 0.01; } b->m_tip_randflipx = p->value("flipX"); b->m_tip_randflipy = p->value("flipY"); } // Transfer settings if (p->value("usePaintDynamics")) { auto jitter_opacity = p->get("opVr"); if (jitter_opacity) { b->m_jitter_opacity = jitter_opacity->value("jitter") * 0.01f; // TODO: jitter_opacity->value("Mnm ") * 0.01f; // minimum size if (jitter_opacity->value("bVTy") == 2) b->m_tip_opacity_pressure = true; } auto jitter_flow = p->get("prVr"); if (jitter_flow) { b->m_jitter_flow = jitter_flow->value("jitter") * 0.01f; // TODO: m_jitter_flow->value("Mnm ") * 0.01f; // minimum size if (jitter_flow->value("bVTy") == 2) b->m_tip_flow_pressure = true; } } // Color Dynamics if (p->value("useColorDynamics")) { b->m_jitter_sat = p->value("Strt") * 0.01f; b->m_jitter_hue = p->value("H ") * 0.01f; b->m_jitter_val = p->value("Brgh") * 0.01f; b->m_jitter_hsv_eachsample = p->value("colorDynamicsPerTip"); } std::vector modes = { "normal", // normal (not in Photoshop) "Mltp", // multiply "Sbtr", // subtract "Drkn", // darken "Ovrl", // overlay "CDdg", // color dodge "CBrn", // color burn "linearBurn", // linear burn "hardMix", // hard mix "linearHeight", // linear height "Hght", // height }; // pattern if (auto patt = p->get("Txtr")) { auto patt_uid = wstr2str(patt->value("Idnt")); b->m_pattern_path = path + "/patterns/" + patt_uid + ".png"; b->m_pattern_thumb_path = path + "/patterns/thumbs/" + patt_uid + ".png"; b->m_pattern_invert = p->value("InvT"); b->m_pattern_eachsample = p->value("TxtC"); b->m_pattern_depth = p->value("textureDepth") * 0.01f; // [0, 100] -> [0, 1] b->m_pattern_scale = p->value("textureScale") * 0.01f; // [0, 1000] -> [0, 1] b->m_pattern_brightness = (float)(p->value("textureBrightness") + 150) / 300.f; // [-150, 150] -> [0, 1] int raw_contrast = p->value("textureContrast"); // [-50, 0, 100] -> [0, 1] b->m_pattern_contrast = raw_contrast / (raw_contrast < 0 ? 100.f : 200.f) + 0.5f; // blending mode std::string blend_mode = p->value("textureBlendMode"); auto bm_it = std::find(modes.begin(), modes.end(), blend_mode); if (bm_it != modes.end()) b->m_pattern_blend_mode = (int)std::distance(modes.begin(), bm_it); b->m_pattern_enabled = p->value("useTexture"); } // dual brush auto db = p->get("dualBrush"); if (db && db->value("useDualBrush")) { auto samp = db->get("Brsh"); if (samp->class_id != "sampledBrush" && samp->class_id != "computedBrush") { LOG("unsupported brush type %s", samp->class_id.c_str()); continue; } //b->m_name = wstr2str(p->value("Nm ")); // default values //b->m_tip_color = { 0, 0, 0, 1 }; b->m_dual_flow = .90f; b->m_dual_opacity = 1.f; b->m_dual_aspect = (1.f - samp->value("Rndn") * 0.01) * 0.5f + 0.5f; b->m_dual_size = samp->value("Dmtr") / b->m_tip_size; b->m_dual_spacing = samp->value("Spcn") * 0.01f; float tip_angle = -samp->value("Angl") / 360.f; b->m_dual_angle = tip_angle >= 0.f ? tip_angle : tip_angle + 1.f; // [-180, 180] -> [0, 1] //b->m_tip_wet = p->value("Wtdg"); //b->m_tip_noise = (float)samp->value("Nose"); b->m_tip_flipx = samp->value("flipX"); b->m_tip_flipy = samp->value("flipY"); b->m_dual_randflip = db->value("Flip"); b->m_dual_scatter_bothaxis = db->value("bothAxes"); if (db->value("useScatter")) { auto scatter = db->get("scatterDynamics"); b->m_dual_scatter = scatter->value("jitter") * 0.01f; } // brush sample if (samp->class_id == "sampledBrush") { auto tip_uid = wstr2str(samp->value("sampledData")); b->m_dual_path = path + "/brushes/" + tip_uid + ".png"; b->m_dual_thumb_path = path + "/brushes/thumbs/" + tip_uid + ".png"; const auto& samp_img = m_samples[tip_uid]; //b->m_tip_width = (float)samp_img->width / (float)samp_img->height; } else if (samp->class_id == "computedBrush") { if (samp->value("Hrdn") > 50.f) { b->m_dual_path = "data/brushes/Round-Hard.png"; b->m_dual_thumb_path = "data/brushes/thumbs/Round-Hard.png"; } else { b->m_dual_path = "data/brushes/Round-Brush.png"; b->m_dual_thumb_path = "data/brushes/thumbs/Round-Brush.png"; } } // blending mode std::string blend_mode = db->value("BlnM"); auto bm_it = std::find(modes.begin(), modes.end(), blend_mode); if (bm_it != modes.end()) b->m_dual_blend_mode = (int)std::distance(modes.begin(), bm_it); b->m_dual_enabled = db->value("useDualBrush"); } ret.push_back(b); } return ret; } bool ABR::open(const std::string& path) { Asset asset; if (asset.open(path.c_str())) { init(asset.read_all(), asset.m_len, BinaryStreamReader::ByteOrder::BigEndian); auto version_major = ru16(); auto version_minor = ru16(); LOG("ABR %d.%d\n", version_major, version_minor); while (!eof()) { if (rstring(4) != "8BIM") return false; auto t = rstring(4); if (t == "desc") { section_desc(); } else if (t == "patt") { section_patt(); } else if (t == "samp") { section_samp(); } else { LOG("skip section %s\n", t.c_str()); skip(align4(ru32())); } } return true; } return false; }