12#include <giomm/file.h>
13#include <glibmm/convert.h>
18#include <glibmm/i18n.h>
19#include <glibmm/miscutils.h>
20#include <glibmm/regex.h>
35Glib::ustring get_extension(Glib::ustring
const &
name) {
36 auto extpos =
name.rfind(
'.');
37 if (extpos != Glib::ustring::npos) {
38 auto ext =
name.substr(extpos).casefold();
44std::vector<uint8_t> read_data(
const Glib::RefPtr<Gio::InputStream>& s,
size_t len) {
45 std::vector<uint8_t>
buf(
len, 0);
50std::string read_string(
const Glib::RefPtr<Gio::InputStream>& s,
size_t len) {
51 std::vector<char>
buf(
len, 0);
53 return std::string(
buf.data(),
len);
57T read_value(
const Glib::RefPtr<Gio::InputStream>& s) {
58 uint8_t
buf[
sizeof(T)];
59 s->read(
buf,
sizeof(T));
61 for (
int i = 0; i <
sizeof(T); ++i) {
68float read_float(
const Glib::RefPtr<Gio::InputStream>& s) {
69 auto val = read_value<uint32_t>(s);
70 return *
reinterpret_cast<float*
>(&val);
73Glib::ustring read_pstring(
const Glib::RefPtr<Gio::InputStream>& s,
bool short_string =
false) {
74 size_t len = short_string ? read_value<uint16_t>(s) : read_value<uint32_t>(s);
77 std::vector<uint16_t>
buf(
len, 0);
78 s->read(
buf.data(), 2 *
len);
79 for (
int i = 0; i <
len; ++i) {
81 c = (
c & 0xff) << 8 |
c >> 8;
87 auto string = g_utf16_to_utf8(
buf.data(),
len,
nullptr,
nullptr,
nullptr);
88 if (!
string)
return {};
90 Glib::ustring ret(
string);
95void skip(
const Glib::RefPtr<Gio::InputStream>& s,
size_t bytes) {
104enum Colorspace : uint16_t
109 GrayscaleColorspace = 8
115void load_acb_palette(
PaletteFileData& palette, std::string
const &fname) {
116 auto file = Gio::File::create_for_path(fname);
117 auto stream = file->read();
118 auto magic = read_string(stream, 4);
119 if (magic !=
"8BCB")
throw std::runtime_error(_(
"ACB file header not recognized."));
121 auto version = read_value<uint16_t>(stream);
123 g_warning(
"Unknown ACB palette version in %s", fname.c_str());
126 read_value<uint16_t>(stream);
128 auto ttl = read_pstring(stream);
129 auto prefix = read_pstring(stream);
130 auto suffix = read_pstring(stream);
131 auto desc = read_pstring(stream);
132 auto extract = [](
const Glib::ustring& str) {
133 auto pos = str.find(
'=');
134 if (pos != Glib::ustring::npos) {
135 return str.substr(pos + 1);
137 return Glib::ustring();
139 prefix = extract(prefix);
140 suffix = extract(suffix);
143 auto color_count = read_value<uint16_t>(stream);
144 palette.
columns = read_value<uint16_t>(stream);
145 palette.
page_offset = read_value<uint16_t>(stream);
146 auto cs = read_value<uint16_t>(stream);
148 auto ext = get_extension(ttl);
151 palette.
name = Glib::path_get_basename(fname);
152 ext = get_extension(palette.
name);
154 palette.
name = palette.
name.substr(0, palette.
name.size() - ext.size());
158 auto r = ttl.find(
"^R");
159 if (r != Glib::ustring::npos) ttl.replace(r, 2,
"®");
163 palette.
colors.reserve(color_count);
165 auto &cm = Manager::get();
166 std::shared_ptr<Space::AnySpace> space;
169 case ColorBook::RgbColorspace:
170 space = cm.find(Space::Type::RGB);
172 case ColorBook::CmykColorspace:
173 space = cm.find(Space::Type::CMYK);
175 case ColorBook::LabColorspace:
176 space = cm.find(Space::Type::LAB);
178 case ColorBook::GrayscaleColorspace:
179 space = cm.find(Space::Type::Gray);
182 throw std::runtime_error(_(
"ACB file color space not supported."));
187 auto name = read_pstring(stream);
189 auto code = read_string(stream, 6);
190 std::ostringstream ost;
193 std::vector<double>
data;
194 for (
auto channel : read_data(stream, space->getComponentCount())) {
195 data.emplace_back(channel / 255.0);
203 color.setName(prefix +
name + suffix);
204 palette.
colors.emplace_back(std::move(color));
209void load_ase_swatches(
PaletteFileData& palette, std::string
const &fname) {
210 auto file = Gio::File::create_for_path(fname);
211 auto stream = file->read();
212 auto magic = read_string(stream, 4);
213 if (magic !=
"ASEF")
throw std::runtime_error(_(
"ASE file header not recognized."));
218 if (version_major > 1) {
219 g_warning(
"Unknown swatches version %d.%d in %s", (
int)version_major, (
int)version_minor, fname.c_str());
222 auto block_count = read_value<uint32_t>(stream);
223 auto &cm = Manager::get();
225 static std::map<std::string, Space::Type> name_map = {
226 {
"RGB ", Space::Type::RGB},
227 {
"LAB ", Space::Type::LAB},
228 {
"CMYK", Space::Type::CMYK},
229 {
"GRAY", Space::Type::Gray}
232 for (uint32_t block = 0; block < block_count; ++block) {
233 auto block_type = read_value<uint16_t>(stream);
234 auto block_length = read_value<uint32_t>(stream);
235 std::ostringstream ost;
237 if (block_type == 0xc001) {
238 auto name = read_pstring(stream,
true);
241 else if (block_type == 0x0001) {
242 auto color_name = read_pstring(stream,
true);
243 auto space_name = read_string(stream, 4);
245 auto it = name_map.find(space_name);
246 if (it == name_map.end()) {
247 std::ostringstream ost;
248 ost << _(
"ASE color mode not recognized:") <<
" '" << space_name <<
"'.";
249 throw std::runtime_error(ost.str());
251 auto space = cm.find(it->second);
252 std::vector<double>
data;
253 for (
unsigned i = 0; i < space->getComponentCount(); i++) {
254 data.emplace_back(read_float(stream));
258 read_value<uint16_t>(stream);
261 color.setName(color_name);
262 palette.
colors.emplace_back(color);
264 else if (block_type == 0xc002) {
267 skip(stream, block_length);
272 palette.
name = Glib::path_get_basename(fname);
273 auto ext = get_extension(palette.
name);
274 if (ext ==
".ase") palette.
name = palette.
name.substr(0, palette.
name.size() - ext.size());
278void load_gimp_palette(
PaletteFileData& palette, std::string
const &path)
280 palette.
name = Glib::path_get_basename(path);
284 if (!f)
throw std::runtime_error(_(
"Failed to open file"));
287 if (!std::fgets(
buf,
sizeof(
buf), f.get()))
throw std::runtime_error(_(
"File is empty"));
288 if (std::strncmp(
"GIMP Palette",
buf, 12) != 0)
throw std::runtime_error(_(
"First line is wrong"));
290 static auto const regex_rgb = Glib::Regex::create(
"\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)\\s*(?:\\s(.*\\S)\\s*)?$", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED);
291 static auto const regex_name = Glib::Regex::create(
"\\s*Name:\\s*(.*\\S)", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED);
292 static auto const regex_cols = Glib::Regex::create(
"\\s*Columns:\\s*(.*\\S)", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED);
293 static auto const regex_blank = Glib::Regex::create(
"\\s*(?:$|#)", Glib::Regex::CompileFlags::OPTIMIZE | Glib::Regex::CompileFlags::ANCHORED);
295 auto &cm = Manager::get();
296 auto space = cm.find(Space::Type::RGB);
298 while (std::fgets(
buf,
sizeof(
buf), f.get())) {
299 Glib::MatchInfo match;
300 if (regex_rgb->match(
buf, match)) {
303 std::vector<double>
data;
304 for (
unsigned i = 0; i < space->getComponentCount(); i++) {
305 data.emplace_back(std::stoi(match.fetch(i+1)) / 255.0);
308 color.setName(match.fetch(4));
310 if (!color.getName().empty()) {
312 color.setName(g_dpgettext2(
nullptr,
"Palette", color.getName().c_str()));
315 palette.
colors.emplace_back(std::move(color));
316 }
else if (regex_name->match(
buf, match)) {
318 palette.
name = match.fetch(1);
319 }
else if (regex_cols->match(
buf, match)) {
321 palette.
columns = std::clamp(std::stoi(match.fetch(1)), 1, 1000);
322 }
else if (regex_blank->match(
buf, match)) {
326 throw std::runtime_error(C_(
"Palette",
"Invalid line ") + std::string(
buf));
337 auto const utf8path = Glib::filename_to_utf8(path);
339 auto compose_error = [&] (
char const *what) {
340 return Glib::ustring::compose(_(
"Error loading palette %1: %2"), utf8path, what);
347 auto const ext = get_extension(utf8path);
349 load_acb_palette(p, path);
350 }
else if (ext ==
".ase") {
351 load_ase_swatches(p, path);
353 load_gimp_palette(p, path);
356 return {std::move(p), {}};
358 }
catch (std::runtime_error
const &e) {
359 return {{}, compose_error(e.what())};
360 }
catch (std::logic_error
const &e) {
361 return {{}, compose_error(e.what())};
362 }
catch (Glib::Error
const &e) {
363 return {{}, compose_error(e.what())};
365 return {{}, Glib::ustring::compose(_(
"Unknown error loading palette %1"), utf8path)};
375 _palettes.emplace_back(std::move(*res.palette));
377 g_warning(
"%s", res.error_message.c_str());
383 return a.name.compare(b.name) < 0;
388 _access.emplace(pal.id.raw(), &pal);
393 if (!pal.name.empty()) {
394 _access.emplace(pal.name.raw(), &pal);
400 auto p =
_access.find(
id.raw());
401 return p !=
_access.end() ? p->second :
nullptr;
405 static std::string current_folder;
406 static std::vector<std::pair<Glib::ustring, Glib::ustring>>
const filters{
407 {_(
"Gimp Color Palette"),
"*.gpl"},
408 {_(
"Adobe Color Book"),
"*.acb"},
409 {_(
"Adobe Swatch Exchange"),
"*.ase"}
411 return choose_file_open(_(
"Load color palette"), window, filters, current_folder);
Singleton class that manages the static list of global palettes.
static GlobalPalettes const & get()
std::unordered_map< std::string, PaletteFileData * > _access
std::vector< PaletteFileData > _palettes
const PaletteFileData * find_palette(const Glib::ustring &id) const
Ad-hoc smart pointer useful when interfacing with C code.
Global color palette information.
A set of useful color modifying functions which do not fit as generic methods on the color class itse...
std::vector< std::string > get_filenames(Type type, std::vector< const char * > const &extensions, std::vector< const char * > const &exclusions)
FILE * fopen_utf8name(char const *utf8name, char const *mode)
Open a file with g_fopen().
Glib::RefPtr< Gio::File > choose_palette_file(Gtk::Window *window)
PaletteResult load_palette(std::string const &path)
auto delete_with(T *p)
Wrap a raw pointer in a std::unique_ptr with a custom function as the deleter.
unsigned int const version_major
unsigned int const version_minor
Glib::RefPtr< Gio::File > choose_file_open(Glib::ustring const &title, Gtk::Window *parent, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters_model, std::string ¤t_folder, Glib::ustring const &accept)
Synchronously run a Gtk::FileDialog to open a single file for reading data.
Inkscape::IO::Resource - simple resource API.
The data loaded from a palette file.
int columns
The preferred number of columns.
Glib::ustring id
Unique ID of this palette.
std::vector< ColorItem > colors
The list of colors in the palette.
Glib::ustring name
Name of the palette, either specified in the file or taken from the filename.
unsigned int page_offset
Index to a representative color of the color block; starts from 0 for each block.