void PaletteEditor::stopsChangedAction() { static const int GradientBufferLastIdx = GradientBufferSize - 1; static const qreal dx = 1.0 / GradientBufferSize; QSize s( m_gradientLabel->maximumSize() ); QRect r( QPoint(0, 0), QSize(s.width(), (s.height() / 2.0 ) ) ); QImage palette_image(s, QImage::Format_RGB32); QPainter painter(&palette_image); GradientStops stops(m_gradientStops->getStops()); qStableSort(stops.begin(), stops.end(), GradientStop::lessThanGradientStopComparison); // now apply the ends and update the palette GradientStops ends( m_gradientEnds->getStops() ); QGradient::Spread spread((QGradient::Spread)m_gradientSpreadGroup->checkedId()); GradientStop n_stop(stops.first()); QRgb ccolor = n_stop.second.rgba(); for (int n = 0, fpos = n_stop.first * GradientBufferSize ; n < fpos ; n++) m_gradient[qMin(n, GradientBufferLastIdx)] = ccolor; int last_stop_idx = stops.size() - 1; for (int begin_idx = 0; begin_idx < last_stop_idx ; begin_idx++) { GradientStop a = stops.at(begin_idx); GradientStop b = stops.at(begin_idx + 1); QColor ac = a.second; QColor bc = b.second; qreal d = ( b.first - a.first ); qreal rdx, gdx, bdx, adx; if (b.colorspace == 0) { rdx = ( (bc.red() - ac.red() ) / d ) * dx; gdx = ( (bc.green() - ac.green() ) / d ) * dx; bdx = ( (bc.blue() - ac.blue() ) / d ) * dx; adx = ( (bc.alpha() - ac.alpha() ) / d ) * dx; } else { rdx = ( (bc.hue() - ac.hue() ) / d ) * dx; gdx = ( (bc.saturation() - ac.saturation() ) / d ) * dx; bdx = ( (bc.value() - ac.value() ) / d ) * dx; adx = ( (bc.alpha() - ac.alpha() ) / d ) * dx; if (b.colorspace == 1) { if (rdx == 0.0) rdx = 180.0 / d * dx; else if (rdx < 0) rdx *= -1; } else { if (rdx == 0.0) rdx = -180.0 / d * dx; else if (rdx > 0) rdx *= -1; } } int n = a.first * GradientBufferSize ; int nb = (int)(b.first * GradientBufferSize ); for (int i = 0 ; n < nb ; i++, n++) { if (b.colorspace == 0) { m_gradient[n] = qRgba( qBound(0, (int)( ac.red() + rdx * i + 0.5 ), 255), qBound(0, (int)( ac.green() + gdx * i + 0.5 ), 255), qBound(0, (int)( ac.blue() + bdx * i + 0.5 ), 255), qBound(0, (int)( ac.alpha() + adx * i + 0.5 ), 255)); } else { int h = (int)( ac.hue() + rdx * i + 0.5 ); if (h < 0) h += 360; m_gradient[n] = QColor::fromHsv(h % 360, qBound(0, (int)( ac.saturation() + gdx * i + 0.5 ), 255), qBound(0, (int)( ac.value() + bdx * i + 0.5 ), 255), qBound(0, (int)( ac.alpha() + adx * i + 0.5 ), 255)).rgba(); } } } n_stop = stops.last(); ccolor = n_stop.second.rgba(); for (int n = n_stop.first * GradientBufferSize ; n < GradientBufferSize ; n++) m_gradient[n] = ccolor; qreal start(ends.at(0).first); qreal end(ends.at(1).first); int begin_idx = start * 256 ; int end_idx = end * 256 ; int ibuf_size = end_idx - begin_idx; flam3_palette_entry* ibuf = new flam3_palette_entry[ibuf_size](); // a very acute filter qreal c2 = 0.01; qreal c3 = 1.0; qreal c4 = 0.01; qreal norm = c2 + c3 + c4; qreal k = 0.0; qreal skip( (GradientBufferSize / 256.0) / qMax(qreal(1.0/GradientBufferSize), end - start) ); for (int n = 0 ; n < ibuf_size ; n++, k += skip) { int j = k; QRgb a2( m_gradient[qBound(0, j + 0, GradientBufferLastIdx)] ); QRgb a3( m_gradient[qMin(j + 1, GradientBufferLastIdx)] ); QRgb a4( m_gradient[qMin(j + 2, GradientBufferLastIdx)] ); ibuf[n].color[0] = (qRed(a2)*c2 + qRed(a3)*c3 + qRed(a4)*c4 ) / (norm * 255.); ibuf[n].color[1] = (qGreen(a2)*c2 + qGreen(a3)*c3 + qGreen(a4)*c4 ) / (norm * 255.); ibuf[n].color[2] = (qBlue(a2)*c2 + qBlue(a3)*c3 + qBlue(a4)*c4 ) / (norm * 255.); ibuf[n].color[3] = (qAlpha(a2)*c2 + qAlpha(a3)*c3 + qAlpha(a4)*c4 ) / (norm * 255.); } // update the gradient editor label painter.fillRect(QRect(QPoint(0,0), s), checkers); if (ibuf_size == 256) { for (int n = 0, h = s.height() ; n < 256 ; n++) { painter.setPen(QColor::fromRgbF(ibuf[n].color[0], ibuf[n].color[1], ibuf[n].color[2], ibuf[n].color[3])); painter.drawLine(n, 0, n, h); } } else { for (int n = 0, h = s.height(), j = 0 ; n < 256 ; n++, j += 4) { QRgb a2( m_gradient[qBound(0, j + 0, GradientBufferLastIdx)] ); QRgb a3( m_gradient[qMin(j + 1, GradientBufferLastIdx)] ); QRgb a4( m_gradient[qMin(j + 2, GradientBufferLastIdx)] ); QRgb r((qRed(a2)*c2 + qRed(a3)*c3 + qRed(a4)*c4 ) / norm ); QRgb g((qGreen(a2)*c2 + qGreen(a3)*c3 + qGreen(a4)*c4 ) / norm ); QRgb b((qBlue(a2)*c2 + qBlue(a3)*c3 + qBlue(a4)*c4 ) / norm ); QRgb a((qAlpha(a2)*c2 + qAlpha(a3)*c3 + qAlpha(a4)*c4 ) / norm ); QColor c(r, g, b, a); painter.setPen(c); painter.drawLine(n, 0, n, h); } } m_gradientLabel->setPixmap(QPixmap::fromImage(palette_image)); // Rescale the gradient colors into the palette with a simple filter if (spread == QGradient::PadSpread) { QRgb fc(m_gradient[0]); flam3_palette_entry e = { 0., { qRed(fc)/255., qGreen(fc)/255., qBlue(fc)/255., qAlpha(fc)/255. }}; for (int n = 0 ; n < begin_idx ; n++) p[n] = e; for (int n = begin_idx, j = 0 ; n < end_idx ; n++, j++) p[n] = ibuf[j]; fc = m_gradient[GradientBufferLastIdx]; e = (flam3_palette_entry){ 1., { qRed(fc)/255., qGreen(fc)/ 255., qBlue(fc)/255., qAlpha(fc)/255. }}; for (int n = end_idx ; n < 256 ; n++) p[n] = e; } else if (spread == QGradient::RepeatSpread) { for (int n = begin_idx, j = 0 ; n < 256 ; n++, j++) p[n] = ibuf[j % ibuf_size]; for (int n = begin_idx - 1, j = ibuf_size * 4096 - 1 ; n >= 0 ; n--, j--) p[n] = ibuf[j % ibuf_size]; } else if (spread == QGradient::ReflectSpread) { for (int n = begin_idx, j = 0, h = 4096*ibuf_size -1 ; n < begin_idx + ibuf_size ; n++, j++, h--) { for (int k = n, q = n + ibuf_size ; k < 256 ; k += 2*ibuf_size, q += 2*ibuf_size ) { p[k] = ibuf[j % ibuf_size]; if (q < 256) p[q] = ibuf[h % ibuf_size]; } } for (int n = begin_idx - 1, j = ibuf_size * 4096 - 1, h = 0 ; n >= begin_idx - ibuf_size ; n--, j--, h++) { for (int k = n, q = n - ibuf_size ; k >= 0 ; k -= 2*ibuf_size, q -= 2*ibuf_size ) { p[k] = ibuf[h % ibuf_size]; if (q >= 0) p[q] = ibuf[j % ibuf_size]; } } } delete[] ibuf; setPaletteView(); emit paletteChanged(); }
int sfc_palette(int argc, char* argv[]) { SfcPalette::Settings settings = {}; bool verbose = false; bool col0_forced = false; rgba_t col0 = 0; try { bool help = false; std::string mode_str; bool dummy = false; Options options; options.IndentDescription = sfc::Constants::options_indent; options.Header = "Usage: superfamiconv palette [<options>]\n"; // clang-format off options.Add(settings.in_image, 'i', "in-image", "Input: image"); options.Add(settings.out_data, 'd', "out-data", "Output: native data"); options.Add(settings.out_act, 'a', "out-act", "Output: photoshop palette"); options.Add(settings.out_json, 'j', "out-json", "Output: json"); options.Add(settings.out_image, 'o', "out-image", "Output: image"); options.Add(mode_str, 'M', "mode", "Mode", std::string("snes"), "Settings"); options.Add(settings.palettes, 'P', "palettes", "Number of subpalettes", unsigned(8), "Settings"); options.Add(settings.colors, 'C', "colors", "Colors per subpalette", unsigned(16), "Settings"); options.Add(settings.tile_w, 'W', "tile-width", "Tile width", unsigned(8), "Settings"); options.Add(settings.tile_h, 'H', "tile-height", "Tile height", unsigned(8), "Settings"); options.AddSwitch(settings.no_remap, 'R', "no-remap", "Don't remap colors", false, "Settings"); options.Add(settings.color_zero, '0', "color-zero", "Set color #0", std::string(), "Settings"); options.AddSwitch(verbose, 'v', "verbose", "Verbose logging", false, "_"); options.AddSwitch(help, 'h', "help", "Show this help", false, "_"); options.AddSwitch(dummy, '9', std::string(), std::string(), false); // clang-format on if (!options.Parse(argc, argv)) return 1; if (argc <= 2 || help) { fmt::print(options.Usage()); return 0; } settings.mode = sfc::mode(mode_str); // Mode-specific defaults if (!options.WasSet("palettes")) settings.palettes = sfc::default_palette_count_for_mode(settings.mode); if (!options.WasSet("colors")) settings.colors = sfc::palette_size_at_bpp(sfc::default_bpp_for_mode(settings.mode)); if (!settings.color_zero.empty()) { col0 = sfc::from_hexstring(settings.color_zero); col0_forced = true; } } catch (const std::exception& e) { fmt::print(stderr, "Error: {}\n", e.what()); return 1; } try { if (settings.in_image.empty()) throw std::runtime_error("Input image required"); sfc::Image image(settings.in_image); if (verbose) fmt::print("Loaded image from \"{}\" ({})\n", settings.in_image, image.description()); sfc::Palette palette; if (settings.no_remap) { if (image.palette_size() == 0) throw std::runtime_error("no-remap requires indexed color image"); if (verbose) fmt::print("Mapping palette straight from indexed color image\n"); palette = sfc::Palette(settings.mode, 1, (unsigned)image.palette_size()); palette.add_colors(image.palette()); } else { if (verbose) fmt::print("Mapping optimized palette ({}x{} entries)\n", settings.palettes, settings.colors); palette = sfc::Palette(settings.mode, settings.palettes, settings.colors); col0 = col0_forced ? col0 : image.crop(0, 0, 1, 1).rgba_data()[0]; if (col0_forced || sfc::col0_is_shared_for_mode(settings.mode)) { if (verbose) fmt::print("Setting color zero to {}\n", sfc::to_hexstring(col0, true, true)); palette.set_col0(col0); } palette.add_images(image.crops(settings.tile_w, settings.tile_h)); } if (verbose) fmt::print("Created palette with {}\n", palette.description()); if (!settings.no_remap) { palette.sort(); } if (!settings.out_data.empty()) { palette.save(settings.out_data); if (verbose) fmt::print("Saved native palette data to \"{}\"\n", settings.out_data); } if (!settings.out_act.empty()) { palette.save_act(settings.out_act); if (verbose) fmt::print("Saved photoshop palette to \"{}\"\n", settings.out_act); } if (!settings.out_image.empty()) { sfc::Image palette_image(palette); palette_image.save(settings.out_image); if (verbose) fmt::print("Saved palette image to \"{}\"\n", settings.out_image); } if (!settings.out_json.empty()) { sfc::write_file(settings.out_json, palette.to_json()); if (verbose) fmt::print("Saved json data to \"{}\"\n", settings.out_json); } } catch (const std::exception& e) { fmt::print(stderr, "Error: {}\n", e.what()); return 1; } return 0; }