core, dialog: Add Procreate swatches import support
Procreate swatches are zipped JSON files. They contain an object array of HSB color palette values. Newer versions of the format also support different color models, spaces, and color profiles. This patch adds support for importing the palette name, colors, and associated color profile in the original HSB format.
This commit is contained in:
parent
9e277c3944
commit
dbc1c55de4
4 changed files with 282 additions and 5 deletions
|
|
@ -567,6 +567,10 @@ gimp_palette_import_from_file (GimpContext *context,
|
|||
palette_list = gimp_palette_load_sbz (context, file, input, error);
|
||||
break;
|
||||
|
||||
case GIMP_PALETTE_FILE_FORMAT_PROCREATE:
|
||||
palette_list = gimp_palette_load_procreate (context, file, input, error);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_set_error (error,
|
||||
GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include <json-glib/json-glib.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <archive.h>
|
||||
|
|
@ -38,11 +40,12 @@
|
|||
|
||||
#include "gimp-intl.h"
|
||||
|
||||
/* Used by SwatchBooker and Procreate color profiles */
|
||||
typedef struct
|
||||
{
|
||||
GimpColorProfile *profile;
|
||||
gchar *id;
|
||||
} SwatchBookerColorProfile;
|
||||
} PaletteColorProfile;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
|
@ -1424,10 +1427,10 @@ gimp_palette_load_sbz (GimpContext *context,
|
|||
|
||||
if (profile)
|
||||
{
|
||||
SwatchBookerColorProfile sbz_profile;
|
||||
PaletteColorProfile sbz_profile;
|
||||
|
||||
sbz_profile.profile = profile;
|
||||
sbz_profile.id = g_strdup (archive_entry_pathname (entry));
|
||||
sbz_profile.id = g_strdup (archive_entry_pathname (entry));
|
||||
|
||||
sbz_data.embedded_profiles =
|
||||
g_list_append (sbz_data.embedded_profiles, &sbz_profile);
|
||||
|
|
@ -1466,6 +1469,261 @@ gimp_palette_load_sbz (GimpContext *context,
|
|||
return g_list_prepend (NULL, sbz_data.palette);
|
||||
}
|
||||
|
||||
GList *
|
||||
gimp_palette_load_procreate (GimpContext *context,
|
||||
GFile *file,
|
||||
GInputStream *input,
|
||||
GError **error)
|
||||
{
|
||||
GimpPalette *palette = NULL;
|
||||
gchar *json_data = NULL;
|
||||
struct archive *a;
|
||||
struct archive_entry *entry;
|
||||
size_t entry_size;
|
||||
int r;
|
||||
|
||||
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
||||
g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
||||
|
||||
if ((a = archive_read_new ()))
|
||||
{
|
||||
const gchar *name = gimp_file_get_utf8_name (file);
|
||||
|
||||
archive_read_support_format_all (a);
|
||||
r = archive_read_open_filename (a, name, 10240);
|
||||
if (r != ARCHIVE_OK)
|
||||
{
|
||||
archive_read_free (a);
|
||||
|
||||
/* TODO: Translate after string freeze */
|
||||
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
||||
"Unable to read Procreate swatches file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (archive_read_next_header (a, &entry) == ARCHIVE_OK)
|
||||
{
|
||||
const gchar *lower = g_ascii_strdown (archive_entry_pathname (entry), -1);
|
||||
|
||||
if (g_str_has_suffix (lower, ".json"))
|
||||
{
|
||||
entry_size = archive_entry_size (entry);
|
||||
json_data = (gchar *) g_malloc (entry_size);
|
||||
|
||||
r = archive_read_data (a, json_data, entry_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (json_data)
|
||||
{
|
||||
JsonParser *parser;
|
||||
JsonReader *reader;
|
||||
GList *profiles = NULL;
|
||||
|
||||
parser = json_parser_new ();
|
||||
if (! json_parser_load_from_data (parser, json_data, entry_size,
|
||||
error))
|
||||
{
|
||||
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
||||
_("Could not read header from palette file '%s': "),
|
||||
gimp_file_get_utf8_name (file));
|
||||
|
||||
g_free (json_data);
|
||||
g_clear_object (&parser);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
reader = json_reader_new (json_parser_get_root (parser));
|
||||
|
||||
if (json_reader_read_member (reader, "name") &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
const gchar *name = json_reader_get_string_value (reader);
|
||||
|
||||
palette = GIMP_PALETTE (gimp_palette_new (context, name));
|
||||
json_reader_end_member (reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
||||
_("Could not read header from palette file '%s': "),
|
||||
gimp_file_get_utf8_name (file));
|
||||
|
||||
g_free (json_data);
|
||||
g_clear_object (&reader);
|
||||
g_clear_object (&parser);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Procreate shows columns in rows of 10 */
|
||||
gimp_palette_set_columns (palette, 10);
|
||||
|
||||
if (json_reader_read_member (reader, "colorProfiles") &&
|
||||
json_reader_is_array (reader))
|
||||
{
|
||||
guint array_length = json_reader_count_elements (reader);
|
||||
|
||||
for (gint i = 0; i < array_length; i++)
|
||||
{
|
||||
PaletteColorProfile *data;
|
||||
GError *test = NULL;
|
||||
|
||||
data = g_malloc0 (sizeof (PaletteColorProfile));
|
||||
data->id = NULL;
|
||||
data->profile = NULL;
|
||||
|
||||
json_reader_read_element (reader, i);
|
||||
if (json_reader_read_member (reader, "hash") &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
const gchar *hash =
|
||||
json_reader_get_string_value (reader);
|
||||
|
||||
data->id = g_strdup (hash);
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
|
||||
if (json_reader_read_member (reader, "iccData") &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
GimpColorProfile *profile = NULL;
|
||||
const gchar *icc_data = NULL;
|
||||
guchar *decoded = NULL;
|
||||
gsize icc_size;
|
||||
|
||||
icc_data = json_reader_get_string_value (reader);
|
||||
decoded = g_base64_decode (icc_data, &icc_size);
|
||||
|
||||
if (decoded)
|
||||
{
|
||||
profile =
|
||||
gimp_color_profile_new_from_icc_profile ((const guint8 *) decoded,
|
||||
icc_size,
|
||||
&test);
|
||||
|
||||
g_free (decoded);
|
||||
}
|
||||
|
||||
if (profile)
|
||||
data->profile = profile;
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
|
||||
if (data->id && data->profile)
|
||||
profiles = g_list_append (profiles, data);
|
||||
|
||||
json_reader_end_element (reader);
|
||||
}
|
||||
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
|
||||
if (json_reader_read_member (reader, "swatches") &&
|
||||
json_reader_is_array (reader))
|
||||
{
|
||||
guint array_length = json_reader_count_elements (reader);
|
||||
const gchar *labels[3] = {"hue", "saturation", "brightness"};
|
||||
guint position = 0;
|
||||
|
||||
for (gint i = 0; i < array_length; i++)
|
||||
{
|
||||
gfloat hsva[4] = { 0, 0, 0, 1 };
|
||||
gint valid = 0;
|
||||
|
||||
json_reader_read_element (reader, i);
|
||||
for (gint j = 0; j < 3; j++)
|
||||
{
|
||||
if (json_reader_read_member (reader, labels[j]) &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
hsva[j] =
|
||||
(gfloat) json_reader_get_double_value (reader);
|
||||
|
||||
valid++;
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
}
|
||||
if (json_reader_read_member (reader, "alpha") &&
|
||||
json_reader_is_value (reader))
|
||||
hsva[3] = (gfloat) json_reader_get_double_value (reader);
|
||||
json_reader_end_member (reader);
|
||||
|
||||
if (valid >= 3)
|
||||
{
|
||||
GeglColor *color = gegl_color_new (NULL);
|
||||
const Babl *space = NULL;
|
||||
|
||||
if (profiles &&
|
||||
json_reader_read_member (reader, "colorProfile") &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
GList *profile_list;
|
||||
const gchar *hash =
|
||||
json_reader_get_string_value (reader);
|
||||
|
||||
for (profile_list = profiles; profile_list;
|
||||
profile_list = g_list_next (profile_list))
|
||||
{
|
||||
PaletteColorProfile *icc = profile_list->data;
|
||||
|
||||
if (! strcmp (icc->id, hash))
|
||||
{
|
||||
space = gimp_color_profile_get_space (icc->profile,
|
||||
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
||||
NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
|
||||
gegl_color_set_pixel (color,
|
||||
babl_format_with_space ("HSV float", space),
|
||||
hsva);
|
||||
|
||||
if (! gimp_palette_find_entry (palette, color, NULL))
|
||||
gimp_palette_add_entry (palette, position, NULL, color);
|
||||
|
||||
if (json_reader_read_member (reader, "name") &&
|
||||
json_reader_is_value (reader))
|
||||
{
|
||||
const gchar *name =
|
||||
json_reader_get_string_value (reader);
|
||||
|
||||
gimp_palette_set_entry_name (palette, position, name);
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
position++;
|
||||
|
||||
g_object_unref (color);
|
||||
}
|
||||
json_reader_end_element (reader);
|
||||
}
|
||||
}
|
||||
json_reader_end_member (reader);
|
||||
|
||||
if (profiles)
|
||||
g_list_free (profiles);
|
||||
|
||||
g_free (json_data);
|
||||
g_clear_object (&reader);
|
||||
g_clear_object (&parser);
|
||||
}
|
||||
|
||||
r = archive_read_free (a);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: Translate after string freeze */
|
||||
g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ,
|
||||
"Unable to read Procreate swatches file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_list_prepend (NULL, palette);
|
||||
}
|
||||
|
||||
static void
|
||||
swatchbooker_load_start_element (GMarkupParseContext *context,
|
||||
const gchar *element_name,
|
||||
|
|
@ -1624,7 +1882,7 @@ swatchbooker_load_text (GMarkupParseContext *context,
|
|||
profile_list;
|
||||
profile_list = g_list_next (profile_list))
|
||||
{
|
||||
SwatchBookerColorProfile *icc = profile_list->data;
|
||||
PaletteColorProfile *icc = profile_list->data;
|
||||
|
||||
if (! strcmp (sbz_data->color_space, icc->id))
|
||||
{
|
||||
|
|
@ -1753,6 +2011,10 @@ gimp_palette_load_detect_format (GFile *file,
|
|||
{
|
||||
format = GIMP_PALETTE_FILE_FORMAT_SBZ;
|
||||
}
|
||||
else if (g_str_has_suffix (lower, ".swatches"))
|
||||
{
|
||||
format = GIMP_PALETTE_FILE_FORMAT_PROCREATE;
|
||||
}
|
||||
|
||||
g_free (lower);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ typedef enum
|
|||
GIMP_PALETTE_FILE_FORMAT_ACB, /* Photoshop ACB color book */
|
||||
GIMP_PALETTE_FILE_FORMAT_ASE, /* Photoshop ASE color palette */
|
||||
GIMP_PALETTE_FILE_FORMAT_CSS, /* Cascaded Stylesheet file (CSS) */
|
||||
GIMP_PALETTE_FILE_FORMAT_SBZ /* Swatchbooker SBZ file */
|
||||
GIMP_PALETTE_FILE_FORMAT_SBZ, /* Swatchbooker SBZ file */
|
||||
GIMP_PALETTE_FILE_FORMAT_PROCREATE /* Procreate .swatches file */
|
||||
} GimpPaletteFileFormat;
|
||||
|
||||
|
||||
|
|
@ -72,6 +73,10 @@ GList * gimp_palette_load_sbz (GimpContext *context,
|
|||
GFile *file,
|
||||
GInputStream *input,
|
||||
GError **error);
|
||||
GList * gimp_palette_load_procreate (GimpContext *context,
|
||||
GFile *file,
|
||||
GInputStream *input,
|
||||
GError **error);
|
||||
|
||||
GimpPaletteFileFormat gimp_palette_load_detect_format (GFile *file,
|
||||
GInputStream *input);
|
||||
|
|
|
|||
|
|
@ -948,6 +948,12 @@ palette_import_file_set_filters (GtkFileChooser *file_chooser)
|
|||
gtk_file_filter_add_pattern (filter, "*.PAL");
|
||||
gtk_file_chooser_add_filter (file_chooser, filter);
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (filter, "Procreate (*.swatches)");
|
||||
gtk_file_filter_add_pattern (filter, "*.swatches");
|
||||
gtk_file_filter_add_pattern (filter, "*.SWATCHES");
|
||||
gtk_file_chooser_add_filter (file_chooser, filter);
|
||||
|
||||
filter = gtk_file_filter_new ();
|
||||
gtk_file_filter_set_name (filter, _("SwatchBooker (*.sbz)"));
|
||||
gtk_file_filter_add_pattern (filter, "*.sbz");
|
||||
|
|
|
|||
Loading…
Reference in a new issue