Manage color profiles (fix #1576)

This commit is contained in:
David Capello 2018-10-18 15:29:16 -03:00
parent f2739d89f1
commit a4d8fc52bf
51 changed files with 1303 additions and 100 deletions

View File

@ -204,7 +204,7 @@ Skia.
You can always check the You can always check the
[official Skia instructions](https://skia.org/user/build) and select [official Skia instructions](https://skia.org/user/build) and select
the OS you are building for. Aseprite uses the `aseprite-m67` Skia the OS you are building for. Aseprite uses the `aseprite-m71` Skia
branch from `https://github.com/aseprite/skia`. branch from `https://github.com/aseprite/skia`.
## Skia on Windows ## Skia on Windows
@ -234,7 +234,7 @@ Then:
Just ignore it.) Just ignore it.)
cd C:\deps cd C:\deps
git clone -b aseprite-m67 https://github.com/aseprite/skia.git git clone -b aseprite-m71 https://github.com/aseprite/skia.git
cd skia cd skia
python tools/git-sync-deps python tools/git-sync-deps
@ -265,7 +265,7 @@ several minutes to finish:
mkdir $HOME/deps mkdir $HOME/deps
cd $HOME/deps cd $HOME/deps
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
git clone -b aseprite-m67 https://github.com/aseprite/skia.git git clone -b aseprite-m71 https://github.com/aseprite/skia.git
export PATH="${PWD}/depot_tools:${PATH}" export PATH="${PWD}/depot_tools:${PATH}"
cd skia cd skia
python tools/git-sync-deps python tools/git-sync-deps
@ -290,7 +290,7 @@ several minutes to finish:
mkdir $HOME/deps mkdir $HOME/deps
cd $HOME/deps cd $HOME/deps
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
git clone -b aseprite-m67 https://github.com/aseprite/skia.git git clone -b aseprite-m71 https://github.com/aseprite/skia.git
export PATH="${PWD}/depot_tools:${PATH}" export PATH="${PWD}/depot_tools:${PATH}"
cd skia cd skia
python tools/git-sync-deps python tools/git-sync-deps

View File

@ -1,5 +1,4 @@
# Aseprite # Aseprite
*Copyright (C) 2001-2018 David Capello*
[![Build Status](https://travis-ci.org/aseprite/aseprite.svg)](https://travis-ci.org/aseprite/aseprite) [![Build Status](https://travis-ci.org/aseprite/aseprite.svg)](https://travis-ci.org/aseprite/aseprite)
[![Build status](https://ci.appveyor.com/api/projects/status/kdu2gt7ls014i25h?svg=true)](https://ci.appveyor.com/project/dacap/aseprite) [![Build status](https://ci.appveyor.com/api/projects/status/kdu2gt7ls014i25h?svg=true)](https://ci.appveyor.com/project/dacap/aseprite)
@ -50,8 +49,12 @@ You can ask for help in:
## Authors ## Authors
* [David Capello](https://davidcapello.com/): Lead developer, bug fixing, new features, designer, and maintainer. [Igara Studio](https://www.igarastudio.com/) is developing Aseprite:
* [Gaspar Capello](https://github.com/Gasparoken): Developer, bug fixing.
* [David Capello](https://davidcapello.com/): Lead developer, fixing
issues, new features, and user support.
* [Gaspar Capello](https://github.com/Gasparoken): Developer, fixing
issues and new features.
## Credits ## Credits

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2014-2018 by David Capello --> <!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2014-2018 David Capello -->
<preferences> <preferences>
<types> <types>
@ -97,6 +98,13 @@
<value id="HSV" value="0" /> <value id="HSV" value="0" />
<value id="HSL" value="1" /> <value id="HSL" value="1" />
</enum> </enum>
<enum id="ColorProfileBehavior">
<value id="DISABLE" value="0" />
<value id="EMBEDDED" value="1" />
<value id="CONVERT" value="2" />
<value id="ASSIGN" value="3" />
<value id="ASK" value="4" />
</enum>
</types> </types>
<global> <global>
@ -301,6 +309,12 @@
<section id="scripts"> <section id="scripts">
<option id="show_run_script_alert" type="bool" default="true" /> <option id="show_run_script_alert" type="bool" default="true" />
</section> </section>
<section id="color">
<option id="manage" type="bool" default="true" />
<option id="working_rgb_space" type="std::string" default="&quot;sRGB&quot;" />
<option id="files_with_profile" type="ColorProfileBehavior" default="ColorProfileBehavior::EMBEDDED" />
<option id="missing_profile" type="ColorProfileBehavior" default="ColorProfileBehavior::ASSIGN" />
</section>
</global> </global>
<tool> <tool>

View File

@ -1,5 +1,6 @@
# Aseprite # Aseprite
# Copyright (C) 2016-2018 by David Capello # Copyright (C) 2018 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello
[advanced_mode] [advanced_mode]
title = Warning - Important title = Warning - Important
@ -565,6 +566,7 @@ dont_show_tooltip = <<<END
Check in case that you want to establish Check in case that you want to establish
the given option as the default option. the given option as the default option.
END END
reset = Reset
[gif_options] [gif_options]
title = GIF Options title = GIF Options
@ -861,6 +863,7 @@ title = Preferences
section_general = General section_general = General
section_files = Files section_files = Files
section_alerts = Alerts section_alerts = Alerts
section_color = Color
section_editor = Editor section_editor = Editor
section_selection = Selection section_selection = Selection
section_timeline = Timeline section_timeline = Timeline
@ -994,7 +997,6 @@ bg_checked = Checked Background
bg_size = Size: bg_size = Size:
bg_apply_zoom = Apply Zoom bg_apply_zoom = Apply Zoom
bg_colors = Colors: bg_colors = Colors:
reset_bg = Reset
grid_visible = Visible Grid grid_visible = Visible Grid
grid_x = X: grid_x = X:
grid_y = Y: grid_y = Y:
@ -1006,7 +1008,6 @@ grid_auto = Auto
grid_pixel_grid_visible = Visible Pixel Grid grid_pixel_grid_visible = Visible Pixel Grid
grid_pixel_grid_color = Color: grid_pixel_grid_color = Color:
grid_pixel_grid_opacity = Opacity: grid_pixel_grid_opacity = Opacity:
reset_grid = Reset
guides = Guides guides = Guides
slices = Slices slices = Slices
layer_edges_color = Layer Edges Color: layer_edges_color = Layer Edges Color:
@ -1038,6 +1039,15 @@ advanced_mode_alert = Show alert when we enter to Advanced Mode
invalid_fg_bg_color_alert = Show alert when drawing with index out of palette bounds invalid_fg_bg_color_alert = Show alert when drawing with index out of palette bounds
run_script_alert = Show alert when we try to run a script run_script_alert = Show alert when we try to run a script
reset_alerts = Reset all alert dialogs reset_alerts = Reset all alert dialogs
color_management = Color Management
working_rgb_cs = Working RGB space:
files_with_cs = Files with profile:
missing_cs = Missing profile:
disable_cs = Don't handle color
use_embedded_cs = Use embedded profile
convert_cs = Convert to working RGB space
assign_cs = Assign working RGB space
ask_cs = Ask
available_themes = Available Themes available_themes = Available Themes
select_theme = &Select select_theme = &Select
download_themes = Download Themes download_themes = Download Themes
@ -1189,6 +1199,9 @@ pixel_ratio = Pixel Aspect Ratio:
square_pixels = Square Pixels (1:1) square_pixels = Square Pixels (1:1)
double_wide = Double-wide Pixels (2:1) double_wide = Double-wide Pixels (2:1)
double_high = Double-high Pixels (1:2) double_high = Double-high Pixels (1:2)
color_profile = Color Profile:
assign_color_profile = Assign
convert_color_profile = Convert
[sprite_size] [sprite_size]
title = Sprite Size title = Sprite Size

View File

@ -1,5 +1,6 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2001-2018 by David Capello --> <!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui> <gui>
<window id="options" text="@.title"> <window id="options" text="@.title">
<vbox> <vbox>
@ -8,6 +9,7 @@
<listbox id="section_listbox"> <listbox id="section_listbox">
<listitem text="@.section_general" value="section_general" /> <listitem text="@.section_general" value="section_general" />
<listitem text="@.section_files" value="section_files" /> <listitem text="@.section_files" value="section_files" />
<listitem text="@.section_color" value="section_color" />
<listitem text="@.section_alerts" value="section_alerts" /> <listitem text="@.section_alerts" value="section_alerts" />
<listitem text="@.section_editor" value="section_editor" /> <listitem text="@.section_editor" value="section_editor" />
<listitem text="@.section_selection" value="section_selection" /> <listitem text="@.section_selection" value="section_selection" />
@ -114,6 +116,38 @@
</grid> </grid>
</vbox> </vbox>
<!-- Color -->
<vbox id="section_color">
<separator text="@.section_color" horizontal="true" />
<check text="@.color_management" id="color_management" pref="color.manage" />
<grid columns="2">
<label text="@.working_rgb_cs" id="working_rgb_cs_label" />
<combobox id="working_rgb_cs" />
<label text="@.files_with_cs" id="files_with_cs_label" />
<combobox id="files_with_cs">
<listitem text="@.disable_cs" />
<listitem text="@.use_embedded_cs" />
<listitem text="@.convert_cs" />
<listitem text="@.assign_cs" />
<listitem text="@.ask_cs" />
</combobox>
<label text="@.missing_cs" id="missing_cs_label" />
<combobox id="missing_cs">
<listitem text="@.disable_cs" />
<listitem text="@.assign_cs" />
<listitem text="@.ask_cs" />
</combobox>
</grid>
<hbox>
<hbox expansive="true" />
<button id="reset_color_management" text="@general.reset" width="60" />
</hbox>
</vbox>
<!-- Editor --> <!-- Editor -->
<vbox id="section_editor"> <vbox id="section_editor">
<separator text="@.section_editor" horizontal="true" /> <separator text="@.section_editor" horizontal="true" />
@ -188,7 +222,7 @@
</combobox> </combobox>
<label text="@.cursor_color_type" /> <label text="@.cursor_color_type" />
<combobox group="1" id="cursor_color_type"> <combobox id="cursor_color_type">
<listitem text="@.cursor_neg_bw" value="0" /> <listitem text="@.cursor_neg_bw" value="0" />
<listitem text="@.cursor_specific_color" value="1" /> <listitem text="@.cursor_specific_color" value="1" />
</combobox> </combobox>
@ -219,7 +253,7 @@
<hbox> <hbox>
<hbox expansive="true" /> <hbox expansive="true" />
<button id="reset_bg" text="@.reset_bg" width="60" /> <button id="reset_bg" text="@general.reset" width="60" />
</hbox> </hbox>
</vbox> </vbox>
@ -269,7 +303,7 @@
<hbox> <hbox>
<hbox expansive="true" /> <hbox expansive="true" />
<button id="reset_grid" text="@.reset_grid" width="60" /> <button id="reset_grid" text="@general.reset" width="60" />
</hbox> </hbox>
</vbox> </vbox>

View File

@ -1,5 +1,6 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2001-2016 by David Capello --> <!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 David Capello -->
<gui> <gui>
<window id="sprite_properties" text="@.title"> <window id="sprite_properties" text="@.title">
<vbox> <vbox>
@ -28,6 +29,15 @@
<listitem text="@.double_high" value="1:2" /> <listitem text="@.double_high" value="1:2" />
</combobox> </combobox>
<label text="@.color_profile" />
<hbox>
<combobox id="color_profile" cell_align="horizontal" expansive="true"></combobox>
<hbox homogeneous="true">
<button id="assign_color_profile" text="@.assign_color_profile">Assign</button>
<button id="convert_color_profile" text="@.convert_color_profile">Convert</button>
</hbox>
</hbox>
</grid> </grid>
<separator horizontal="true" /> <separator horizontal="true" />
<hbox> <hbox>

View File

@ -1,7 +1,5 @@
# Aseprite File Format (.ase/.aseprite) Specifications # Aseprite File Format (.ase/.aseprite) Specifications
> Copyright (C) 2001-2018 by David Capello
1. [References](#references) 1. [References](#references)
2. [Introduction](#introduction) 2. [Introduction](#introduction)
3. [Header](#header) 3. [Header](#header)
@ -213,6 +211,26 @@ Adds extra information to the latest read cel.
FIXED Height of the cel in the sprite FIXED Height of the cel in the sprite
BYTE[16] For future use (set to zero) BYTE[16] For future use (set to zero)
### Color Profile Chunk (0x2007)
Color profile for RGB or grayscale values.
WORD Type
0 - no color profile (as in old .aseprite files)
1 - use sRGB
2 - use the embedded ICC profile
WORD Flags
1 - use special fixed gamma
FIXED Fixed gamma (1.0 = linear)
Note: The gamma in sRGB is 2.2 in overall but it doesn't use
a this fixed gamma, because sRGB uses different gamma sections
(linear and non-linear). If sRGB is specified with a fixed
gamma = 1.0, it means that this is Linear sRGB.
BYTE[8] Reserved (set to zero]
+ If type = ICC:
DWORD ICC profile data length
BYTE[] ICC profile data. More info: http://www.color.org/ICC1V42.pdf
### Mask Chunk (0x2016) DEPRECATED ### Mask Chunk (0x2016) DEPRECATED
SHORT X position SHORT X position

2
laf

@ -1 +1 @@
Subproject commit ba09f989212548d8fc5659cb716398f9407c351a Subproject commit f4a08a23e8de9a482c8167ae084f4148803673be

View File

@ -16,11 +16,6 @@ if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LTCG") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LTCG")
endif() endif()
# Do not link with libcmt.lib (to avoid duplicated symbols with msvcrtd.lib)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NODEFAULTLIB:LIBCMT")
endif()
add_definitions(-D_SCL_SECURE_NO_WARNINGS) add_definitions(-D_SCL_SECURE_NO_WARNINGS)
endif(MSVC) endif(MSVC)

View File

@ -28,31 +28,28 @@ because they don't depend on any other component.
* [cfg](cfg/) (base): Library to load/save .ini files. * [cfg](cfg/) (base): Library to load/save .ini files.
* [gen](gen/) (base): Helper utility to generate C++ files from different XMLs. * [gen](gen/) (base): Helper utility to generate C++ files from different XMLs.
* [net](net/) (base): Networking library to send HTTP requests. * [net](net/) (base): Networking library to send HTTP requests.
* laf/[os](https://github.com/aseprite/laf/tree/master/os) (base, gfx, wacom): OS input/output.
## Level 2 ## Level 2
* [doc](doc/) (base, fixmath, gfx): Document model library. * [doc](doc/) (base, fixmath, gfx, os): Document model library.
* laf/[os](https://github.com/aseprite/laf/tree/master/os) (base, gfx, wacom): OS input/output.
## Level 3
* [filters](filters/) (base, doc, gfx): Effects for images.
* [render](render/) (base, doc, gfx): Library to render documents.
* [ui](ui/) (base, gfx, os): Portable UI library (buttons, windows, text fields, etc.) * [ui](ui/) (base, gfx, os): Portable UI library (buttons, windows, text fields, etc.)
* [updater](updater/) (base, cfg, net): Component to check for updates. * [updater](updater/) (base, cfg, net): Component to check for updates.
## Level 4 ## Level 3
* [dio](dio/) (base, doc, fixmath, flic): Load/save sprites/documents. * [dio](dio/) (base, doc, fixmath, flic): Load/save sprites/documents.
* [filters](filters/) (base, doc, gfx): Effects for images.
* [render](render/) (base, doc, gfx): Library to render documents.
## Level 4
* [app](app/) (base, doc, dio, filters, fixmath, flic, gfx, pen, render, scripting, os, ui, undo, updater)
* [desktop](desktop/) (base, doc, dio, render): Integration with the desktop (Windows Explorer, Finder, GNOME, KDE, etc.)
## Level 5 ## Level 5
* [app](app/) (base, doc, dio, filters, fixmath, flic, gfx, pen, render, scripting, os, ui, undo, updater)
## Level 6
* [main](main/) (app, base, os, ui) * [main](main/) (app, base, os, ui)
* [desktop](desktop/) (base, doc, dio, render): Integration with the desktop (Windows Explorer, Finder, GNOME, KDE, etc.)
# Debugging Tricks # Debugging Tricks

View File

@ -1,4 +1,5 @@
# Aseprite # Aseprite
# Copyright (C) 2018 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello # Copyright (C) 2001-2018 David Capello
# Generate a ui::Widget for each widget in a XML file # Generate a ui::Widget for each widget in a XML file
@ -431,12 +432,14 @@ add_library(app-lib
cmd/add_layer.cpp cmd/add_layer.cpp
cmd/add_palette.cpp cmd/add_palette.cpp
cmd/add_slice.cpp cmd/add_slice.cpp
cmd/assign_color_profile.cpp
cmd/background_from_layer.cpp cmd/background_from_layer.cpp
cmd/clear_cel.cpp cmd/clear_cel.cpp
cmd/clear_image.cpp cmd/clear_image.cpp
cmd/clear_mask.cpp cmd/clear_mask.cpp
cmd/clear_rect.cpp cmd/clear_rect.cpp
cmd/configure_background.cpp cmd/configure_background.cpp
cmd/convert_color_profile.cpp
cmd/copy_cel.cpp cmd/copy_cel.cpp
cmd/copy_frame.cpp cmd/copy_frame.cpp
cmd/copy_rect.cpp cmd/copy_rect.cpp
@ -500,6 +503,7 @@ add_library(app-lib
cmd_transaction.cpp cmd_transaction.cpp
color.cpp color.cpp
color_picker.cpp color_picker.cpp
color_spaces.cpp
color_utils.cpp color_utils.cpp
commands/cmd_background_from_layer.cpp commands/cmd_background_from_layer.cpp
commands/cmd_cel_opacity.cpp commands/cmd_cel_opacity.cpp

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -119,6 +120,7 @@ namespace app {
// App Signals // App Signals
obs::signal<void()> Exit; obs::signal<void()> Exit;
obs::signal<void()> PaletteChange; obs::signal<void()> PaletteChange;
obs::signal<void()> ColorSpaceChange;
private: private:
class CoreModules; class CoreModules;

View File

@ -0,0 +1,49 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/assign_color_profile.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/sprite.h"
namespace app {
namespace cmd {
AssignColorProfile::AssignColorProfile(doc::Sprite* sprite, const gfx::ColorSpacePtr& cs)
: WithSprite(sprite)
, m_oldCS(sprite->colorSpace())
, m_newCS(cs)
{
}
void AssignColorProfile::onExecute()
{
doc::Sprite* spr = sprite();
spr->setColorSpace(m_newCS);
spr->incrementVersion();
}
void AssignColorProfile::onUndo()
{
doc::Sprite* spr = sprite();
spr->setColorSpace(m_oldCS);
spr->incrementVersion();
}
void AssignColorProfile::onFireNotifications()
{
doc::Sprite* sprite = this->sprite();
Doc* doc = static_cast<Doc*>(sprite->document());
doc->notifyColorSpaceChanged();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,42 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_ASSIGN_COLOR_PROFILE_H_INCLUDED
#define APP_CMD_ASSIGN_COLOR_PROFILE_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "gfx/color_space.h"
namespace app {
namespace cmd {
class AssignColorProfile : public Cmd,
public WithSprite {
public:
AssignColorProfile(doc::Sprite* sprite, const gfx::ColorSpacePtr& cs);
protected:
void onExecute() override;
void onUndo() override;
void onFireNotifications() override;
size_t onMemSize() const override {
return sizeof(*this) +
2*sizeof(gfx::ColorSpace) +
m_oldCS->iccSize() +
m_newCS->iccSize();
}
private:
gfx::ColorSpacePtr m_oldCS;
gfx::ColorSpacePtr m_newCS;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -0,0 +1,104 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/convert_color_profile.h"
#include "app/cmd/assign_color_profile.h"
#include "app/cmd/replace_image.h"
#include "app/cmd/set_palette.h"
#include "app/doc.h"
#include "doc/cels_range.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "os/color_space.h"
#include "os/system.h"
namespace app {
namespace cmd {
ConvertColorProfile::ConvertColorProfile(doc::Sprite* sprite, const gfx::ColorSpacePtr& newCS)
: WithSprite(sprite)
{
os::System* system = os::instance();
ASSERT(sprite->colorSpace());
ASSERT(newCS);
auto srcOCS = system->createColorSpace(sprite->colorSpace());
auto dstOCS = system->createColorSpace(newCS);
ASSERT(srcOCS);
ASSERT(dstOCS);
auto conversion = system->convertBetweenColorSpace(srcOCS, dstOCS);
// Convert images
if (sprite->pixelFormat() == doc::IMAGE_RGB) {
for (Cel* cel : sprite->uniqueCels()) {
ImageRef old_image = cel->imageRef();
ImageSpec spec = old_image->spec();
spec.setColorSpace(newCS);
ImageRef new_image(Image::create(spec));
if (conversion) {
for (int y=0; y<spec.height(); ++y) {
conversion->convert((uint32_t*)new_image->getPixelAddress(0, y),
(const uint32_t*)old_image->getPixelAddress(0, y),
spec.width());
}
}
else {
new_image->copy(old_image.get(), gfx::Clip(0, 0, old_image->bounds()));
}
m_seq.add(new cmd::ReplaceImage(sprite, old_image, new_image));
}
}
if (conversion) {
// Convert palette
if (sprite->pixelFormat() != doc::IMAGE_GRAYSCALE) {
for (auto& pal : sprite->getPalettes()) {
Palette newPal(pal->frame(), pal->size());
for (int i=0; i<pal->size(); ++i) {
color_t oldCol = pal->entry(i);
color_t newCol = pal->entry(i);
conversion->convert((uint32_t*)&newCol,
(const uint32_t*)&oldCol, 1);
newPal.setEntry(i, newCol);
}
if (*pal != newPal)
m_seq.add(new cmd::SetPalette(sprite, pal->frame(), &newPal));
}
}
}
m_seq.add(new cmd::AssignColorProfile(sprite, newCS));
}
void ConvertColorProfile::onExecute()
{
m_seq.execute(context());
}
void ConvertColorProfile::onUndo()
{
m_seq.undo();
}
void ConvertColorProfile::onRedo()
{
m_seq.redo();
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,43 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_CONVERT_COLOR_PROFILE_H_INCLUDED
#define APP_CMD_CONVERT_COLOR_PROFILE_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "app/cmd_sequence.h"
#include "gfx/color_space.h"
namespace gfx {
class ColorSpace;
}
namespace app {
namespace cmd {
class ConvertColorProfile : public Cmd,
public WithSprite {
public:
ConvertColorProfile(doc::Sprite* sprite, const gfx::ColorSpacePtr& newCS);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override {
return sizeof(*this) + m_seq.memSize();
}
private:
CmdSequence m_seq;
};
} // namespace cmd
} // namespace app
#endif

79
src/app/color_spaces.cpp Normal file
View File

@ -0,0 +1,79 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/color_spaces.h"
#include "app/doc.h"
#include "app/modules/editors.h"
#include "app/ui/editor/editor.h"
#include "os/display.h"
#include "os/system.h"
namespace app {
os::ColorSpacePtr get_screen_color_space()
{
return os::instance()->defaultDisplay()->colorSpace();
}
os::ColorSpacePtr get_current_color_space()
{
if (current_editor)
return current_editor->document()->osColorSpace();
else
return get_screen_color_space();
}
//////////////////////////////////////////////////////////////////////
// Color conversion
ConvertCS::ConvertCS()
{
auto srcCS = get_current_color_space();
auto dstCS = get_screen_color_space();
if (srcCS && dstCS)
m_conversion = os::instance()->convertBetweenColorSpace(srcCS, dstCS);
}
ConvertCS::ConvertCS(const os::ColorSpacePtr& srcCS,
const os::ColorSpacePtr& dstCS)
{
m_conversion = os::instance()->convertBetweenColorSpace(srcCS, dstCS);
}
ConvertCS::ConvertCS(ConvertCS&& that)
: m_conversion(std::move(that.m_conversion))
{
}
gfx::Color ConvertCS::operator()(const gfx::Color c)
{
if (m_conversion) {
gfx::Color out;
m_conversion->convert((uint32_t*)&out, (const uint32_t*)&c, 1);
return out;
}
else {
return c;
}
}
ConvertCS convert_from_current_to_screen_color_space()
{
return ConvertCS();
}
ConvertCS convert_from_custom_to_srgb(const os::ColorSpacePtr& from)
{
return ConvertCS(from,
os::instance()->createColorSpace(gfx::ColorSpace::MakeSRGB()));
}
} // namespace app

40
src/app/color_spaces.h Normal file
View File

@ -0,0 +1,40 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_COLOR_SPACES_H_INCLUDED
#define APP_COLOR_SPACES_H_INCLUDED
#pragma once
#include "gfx/color.h"
#include "os/color_space.h"
#include <memory>
namespace app {
os::ColorSpacePtr get_screen_color_space();
// Returns the color space of the current document.
os::ColorSpacePtr get_current_color_space();
class ConvertCS {
public:
ConvertCS();
ConvertCS(const os::ColorSpacePtr& srcCS,
const os::ColorSpacePtr& dstCS);
ConvertCS(ConvertCS&&);
ConvertCS& operator=(const ConvertCS&) = delete;
gfx::Color operator()(const gfx::Color c);
private:
std::unique_ptr<os::ColorSpaceConversion> m_conversion;
};
ConvertCS convert_from_current_to_screen_color_space();
ConvertCS convert_from_custom_to_srgb(const os::ColorSpacePtr& from);
} // namespace app
#endif

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -49,10 +50,35 @@ static const char* kSectionExtensionsId = "section_extensions";
static const char* kInfiniteSymbol = "\xE2\x88\x9E"; // Infinite symbol (UTF-8) static const char* kInfiniteSymbol = "\xE2\x88\x9E"; // Infinite symbol (UTF-8)
static app::gen::ColorProfileBehavior filesWithCsMap[] = {
app::gen::ColorProfileBehavior::DISABLE,
app::gen::ColorProfileBehavior::EMBEDDED,
app::gen::ColorProfileBehavior::CONVERT,
app::gen::ColorProfileBehavior::ASSIGN,
app::gen::ColorProfileBehavior::ASK,
};
static app::gen::ColorProfileBehavior missingCsMap[] = {
app::gen::ColorProfileBehavior::DISABLE,
app::gen::ColorProfileBehavior::ASSIGN,
app::gen::ColorProfileBehavior::ASK,
};
using namespace ui; using namespace ui;
class OptionsWindow : public app::gen::Options { class OptionsWindow : public app::gen::Options {
class ColorSpaceItem : public ListItem {
public:
ColorSpaceItem(const os::ColorSpacePtr& cs)
: ListItem(cs->gfxColorSpace()->name()),
m_cs(cs) {
}
os::ColorSpacePtr cs() const { return m_cs; }
private:
os::ColorSpacePtr m_cs;
};
class ThemeItem : public ListItem { class ThemeItem : public ListItem {
public: public:
ThemeItem(const std::string& path, ThemeItem(const std::string& path,
@ -153,6 +179,21 @@ public:
recentFiles()->setValue(m_pref.general.recentItems()); recentFiles()->setValue(m_pref.general.recentItems());
clearRecentFiles()->Click.connect(base::Bind<void>(&OptionsWindow::onClearRecentFiles, this)); clearRecentFiles()->Click.connect(base::Bind<void>(&OptionsWindow::onClearRecentFiles, this));
// Color profiles
resetColorManagement()->Click.connect(base::Bind<void>(&OptionsWindow::onResetColorManagement, this));
colorManagement()->Click.connect(base::Bind<void>(&OptionsWindow::onColorManagement, this));
{
os::instance()->listColorSpaces(m_colorSpaces);
for (auto& cs : m_colorSpaces) {
if (cs->gfxColorSpace()->type() != gfx::ColorSpace::None)
workingRgbCs()->addItem(new ColorSpaceItem(cs));
}
updateColorProfileControls(m_pref.color.manage(),
m_pref.color.workingRgbSpace(),
m_pref.color.filesWithProfile(),
m_pref.color.missingProfile());
}
// Alerts // Alerts
resetAlerts()->Click.connect(base::Bind<void>(&OptionsWindow::onResetAlerts, this)); resetAlerts()->Click.connect(base::Bind<void>(&OptionsWindow::onResetAlerts, this));
@ -444,6 +485,14 @@ public:
m_pref.guides.autoGuidesColor(autoGuidesColor()->getColor()); m_pref.guides.autoGuidesColor(autoGuidesColor()->getColor());
m_pref.slices.defaultColor(defaultSliceColor()->getColor()); m_pref.slices.defaultColor(defaultSliceColor()->getColor());
m_pref.color.workingRgbSpace(
workingRgbCs()->getItemText(
workingRgbCs()->getSelectedItemIndex()));
m_pref.color.filesWithProfile(
filesWithCsMap[filesWithCs()->getSelectedItemIndex()]);
m_pref.color.missingProfile(
missingCsMap[missingCs()->getSelectedItemIndex()]);
m_curPref->show.grid(gridVisible()->isSelected()); m_curPref->show.grid(gridVisible()->isSelected());
m_curPref->grid.bounds(gridBounds()); m_curPref->grid.bounds(gridBounds());
m_curPref->grid.color(gridColor()->getColor()); m_curPref->grid.color(gridColor()->getColor());
@ -627,6 +676,53 @@ private:
App::instance()->recentFiles()->clear(); App::instance()->recentFiles()->clear();
} }
void onColorManagement() {
const bool state = colorManagement()->isSelected();
workingRgbCsLabel()->setEnabled(state);
workingRgbCs()->setEnabled(state);
filesWithCsLabel()->setEnabled(state);
filesWithCs()->setEnabled(state);
missingCsLabel()->setEnabled(state);
missingCs()->setEnabled(state);
}
void onResetColorManagement() {
updateColorProfileControls(m_pref.color.manage.defaultValue(),
m_pref.color.workingRgbSpace.defaultValue(),
m_pref.color.filesWithProfile.defaultValue(),
m_pref.color.missingProfile.defaultValue());
}
void updateColorProfileControls(const bool manage,
const std::string& workingRgbSpace,
const app::gen::ColorProfileBehavior& filesWithProfile,
const app::gen::ColorProfileBehavior& missingProfile) {
colorManagement()->setSelected(manage);
for (auto child : *workingRgbCs()) {
if (child->text() == workingRgbSpace) {
workingRgbCs()->setSelectedItem(child);
break;
}
}
for (int i=0; i<sizeof(filesWithCsMap)/sizeof(filesWithCsMap[0]); ++i) {
if (filesWithCsMap[i] == filesWithProfile) {
filesWithCs()->setSelectedItemIndex(i);
break;
}
}
for (int i=0; i<sizeof(missingCsMap)/sizeof(missingCsMap[0]); ++i) {
if (missingCsMap[i] == missingProfile) {
missingCs()->setSelectedItemIndex(i);
break;
}
}
onColorManagement();
}
void onResetAlerts() { void onResetAlerts() {
fileFormatDoesntSupportAlert()->resetWithDefaultValue(); fileFormatDoesntSupportAlert()->resetWithDefaultValue();
exportAnimationInSequenceAlert()->resetWithDefaultValue(); exportAnimationInSequenceAlert()->resetWithDefaultValue();
@ -1124,6 +1220,7 @@ private:
std::string m_restoreThisTheme; std::string m_restoreThisTheme;
int m_restoreScreenScaling; int m_restoreScreenScaling;
int m_restoreUIScaling; int m_restoreUIScaling;
std::vector<os::ColorSpacePtr> m_colorSpaces;
}; };
class OptionsCommand : public Command { class OptionsCommand : public Command {

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -8,12 +9,15 @@
#include "config.h" #include "config.h"
#endif #endif
#include "app/cmd/assign_color_profile.h"
#include "app/cmd/convert_color_profile.h"
#include "app/cmd/set_pixel_ratio.h" #include "app/cmd/set_pixel_ratio.h"
#include "app/color.h" #include "app/color.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/doc_api.h" #include "app/doc_api.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/tx.h" #include "app/tx.h"
#include "app/ui/color_button.h" #include "app/ui/color_button.h"
#include "app/util/pixel_ratio.h" #include "app/util/pixel_ratio.h"
@ -22,12 +26,13 @@
#include "doc/image.h" #include "doc/image.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "fmt/format.h"
#include "os/color_space.h"
#include "os/system.h"
#include "ui/ui.h" #include "ui/ui.h"
#include "sprite_properties.xml.h" #include "sprite_properties.xml.h"
#include <cstdio>
namespace app { namespace app {
using namespace ui; using namespace ui;
@ -56,11 +61,15 @@ bool SpritePropertiesCommand::onEnabled(Context* context)
void SpritePropertiesCommand::onExecute(Context* context) void SpritePropertiesCommand::onExecute(Context* context)
{ {
std::string imgtype_text; std::string imgtype_text;
char buf[256]; ColorButton* color_button = nullptr;
ColorButton* color_button = NULL;
// List of available color profiles
std::vector<os::ColorSpacePtr> colorSpaces;
os::instance()->listColorSpaces(colorSpaces);
// Load the window widget // Load the window widget
app::gen::SpriteProperties window; app::gen::SpriteProperties window;
int selectedColorProfile = -1;
// Get sprite properties and fill frame fields // Get sprite properties and fill frame fields
{ {
@ -77,8 +86,8 @@ void SpritePropertiesCommand::onExecute(Context* context)
imgtype_text = "Grayscale"; imgtype_text = "Grayscale";
break; break;
case IMAGE_INDEXED: case IMAGE_INDEXED:
std::sprintf(buf, "Indexed (%d colors)", sprite->palette(0)->size()); imgtype_text = fmt::format("Indexed ({0} colors)",
imgtype_text = buf; sprite->palette(0)->size());
break; break;
default: default:
ASSERT(false); ASSERT(false);
@ -116,6 +125,64 @@ void SpritePropertiesCommand::onExecute(Context* context)
// Pixel ratio // Pixel ratio
window.pixelRatio()->setValue( window.pixelRatio()->setValue(
base::convert_to<std::string>(sprite->pixelRatio())); base::convert_to<std::string>(sprite->pixelRatio()));
// Color profile
selectedColorProfile = -1;
int i = 0;
for (auto& cs : colorSpaces) {
if (cs->gfxColorSpace()->nearlyEqual(*sprite->colorSpace())) {
selectedColorProfile = i;
break;
}
++i;
}
if (selectedColorProfile < 0) {
colorSpaces.push_back(os::instance()->createColorSpace(sprite->colorSpace()));
selectedColorProfile = colorSpaces.size()-1;
}
for (auto& cs : colorSpaces)
window.colorProfile()->addItem(cs->gfxColorSpace()->name());
window.colorProfile()->setSelectedItemIndex(selectedColorProfile);
auto updateButtons =
[&] {
bool enabled = (selectedColorProfile != window.colorProfile()->getSelectedItemIndex());
window.assignColorProfile()->setEnabled(enabled);
window.convertColorProfile()->setEnabled(enabled);
window.ok()->setEnabled(!enabled);
};
window.assignColorProfile()->setEnabled(false);
window.convertColorProfile()->setEnabled(false);
window.colorProfile()->Change.connect(updateButtons);
window.assignColorProfile()->Click.connect(
[&](Event&){
selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), "Assign Color Profile");
tx(new cmd::AssignColorProfile(
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
tx.commit();
updateButtons();
});
window.convertColorProfile()->Click.connect(
[&](Event&){
selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
ContextWriter writer(context);
Sprite* sprite(writer.sprite());
Tx tx(writer.context(), "Convert Color Profile");
tx(new cmd::ConvertColorProfile(
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
tx.commit();
updateButtons();
});
} }
window.remapWindow(); window.remapWindow();

View File

@ -140,7 +140,9 @@ void BackupObserver::backgroundThread()
diff.frameTags ? "frameTags": "", diff.frameTags ? "frameTags": "",
diff.palettes ? "palettes": "", diff.palettes ? "palettes": "",
diff.layers ? "layers": "", diff.layers ? "layers": "",
diff.cels ? "cels": ""); diff.cels ? "cels": "",
diff.images ? "images": "",
diff.colorProfiles ? "colorProfiles": "");
Doc* copyDoc = copy.release(); Doc* copyDoc = copy.release();
ui::execute_from_ui_thread( ui::execute_from_ui_thread(

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -35,6 +36,7 @@
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/string_io.h" #include "doc/string_io.h"
#include "doc/subobjects_io.h" #include "doc/subobjects_io.h"
#include "fixmath/fixmath.h"
#include <fstream> #include <fstream>
#include <map> #include <map>
@ -298,9 +300,30 @@ private:
} }
} }
// Read color space
gfx::ColorSpacePtr colorSpace = readColorSpace(s);
if (colorSpace)
spr->setColorSpace(colorSpace);
return spr.release(); return spr.release();
} }
gfx::ColorSpacePtr readColorSpace(std::ifstream& s) {
const gfx::ColorSpace::Type type = (gfx::ColorSpace::Type)read16(s);
const gfx::ColorSpace::Flag flags = (gfx::ColorSpace::Flag)read16(s);
const double gamma = fixmath::fixtof(read32(s));
const size_t n = read32(s);
std::vector<uint8_t> buf(n);
if (n)
s.read((char*)&buf[0], n);
std::string name = read_string(s);
auto colorSpace = std::make_shared<gfx::ColorSpace>(
type, flags, gamma, std::move(buf));
colorSpace->setName(name);
return colorSpace;
}
// TODO could we use doc::read_layer() here? // TODO could we use doc::read_layer() here?
Layer* readLayer(std::ifstream& s) { Layer* readLayer(std::ifstream& s) {
LayerFlags flags = (LayerFlags)read32(s); LayerFlags flags = (LayerFlags)read32(s);

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -33,6 +34,7 @@
#include "doc/slice_io.h" #include "doc/slice_io.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/string_io.h" #include "doc/string_io.h"
#include "fixmath/fixmath.h"
#include <fstream> #include <fstream>
#include <map> #include <map>
@ -165,6 +167,23 @@ private:
for (const Slice* slice : spr->slices()) for (const Slice* slice : spr->slices())
write32(s, slice->id()); write32(s, slice->id());
// Color Space
writeColorSpace(s, spr->colorSpace());
return true;
}
bool writeColorSpace(std::ofstream& s, const gfx::ColorSpacePtr& colorSpace) {
write16(s, colorSpace->type());
write16(s, colorSpace->flags());
write32(s, fixmath::ftofix(colorSpace->gamma()));
auto& rawData = colorSpace->rawData();
write32(s, rawData.size());
if (rawData.size() > 0)
s.write((const char*)&rawData[0], rawData.size());
write_string(s, colorSpace->name());
return true; return true;
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -31,6 +32,8 @@
#include "doc/mask_boundaries.h" #include "doc/mask_boundaries.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "os/display.h"
#include "os/system.h"
#include <limits> #include <limits>
#include <map> #include <map>
@ -54,6 +57,8 @@ Doc::Doc(Sprite* sprite)
if (sprite) if (sprite)
sprites().add(sprite); sprites().add(sprite);
updateOSColorSpace(false);
} }
Doc::~Doc() Doc::~Doc()
@ -111,6 +116,15 @@ void Doc::notifyGeneralUpdate()
notify_observers<DocEvent&>(&DocObserver::onGeneralUpdate, ev); notify_observers<DocEvent&>(&DocObserver::onGeneralUpdate, ev);
} }
void Doc::notifyColorSpaceChanged()
{
updateOSColorSpace(true);
DocEvent ev(this);
ev.sprite(sprite());
notify_observers<DocEvent&>(&DocObserver::onColorSpaceChanged, ev);
}
void Doc::notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame) void Doc::notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame)
{ {
DocEvent ev(this); DocEvent ev(this);
@ -482,6 +496,22 @@ void Doc::removeFromContext()
} }
} }
void Doc::updateOSColorSpace(bool appWideSignal)
{
auto system = os::instance();
if (system) {
m_osColorSpace = system->createColorSpace(sprite()->colorSpace());
if (!m_osColorSpace && system->defaultDisplay())
m_osColorSpace = system->defaultDisplay()->colorSpace();
}
if (appWideSignal &&
context() &&
context()->activeDocument() == this) {
App::instance()->ColorSpaceChange();
}
}
// static // static
gfx::Point Doc::NoLastDrawingPoint() gfx::Point Doc::NoLastDrawingPoint()
{ {

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -23,6 +24,7 @@
#include "doc/pixel_format.h" #include "doc/pixel_format.h"
#include "gfx/rect.h" #include "gfx/rect.h"
#include "obs/observable.h" #include "obs/observable.h"
#include "os/color_space.h"
#include <string> #include <string>
@ -81,10 +83,13 @@ namespace app {
color_t bgColor() const; color_t bgColor() const;
color_t bgColor(Layer* layer) const; color_t bgColor(Layer* layer) const;
os::ColorSpacePtr osColorSpace() const { return m_osColorSpace; }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Notifications // Notifications
void notifyGeneralUpdate(); void notifyGeneralUpdate();
void notifyColorSpaceChanged();
void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame); void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame);
void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region); void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region);
void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer); void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer);
@ -120,7 +125,7 @@ namespace app {
// Loaded options from file // Loaded options from file
void setFormatOptions(const base::SharedPtr<FormatOptions>& format_options); void setFormatOptions(const base::SharedPtr<FormatOptions>& format_options);
base::SharedPtr<FormatOptions> getFormatOptions() { return m_format_options; } base::SharedPtr<FormatOptions> getFormatOptions() const { return m_format_options; }
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Boundaries // Boundaries
@ -188,6 +193,7 @@ namespace app {
private: private:
void removeFromContext(); void removeFromContext();
void updateOSColorSpace(bool appWideSignal);
Context* m_ctx; Context* m_ctx;
int m_flags; int m_flags;
@ -212,6 +218,9 @@ namespace app {
gfx::Point m_lastDrawingPoint; gfx::Point m_lastDrawingPoint;
// Last used color space to render a sprite.
os::ColorSpacePtr m_osColorSpace;
DISABLE_COPYING(Doc); DISABLE_COPYING(Doc);
}; };

View File

@ -138,6 +138,11 @@ DocDiff compare_docs(const Doc* a,
} }
} }
// Compare color spaces
if (!a->sprite()->colorSpace()->nearlyEqual(*b->sprite()->colorSpace())) {
diff.anything = diff.colorProfiles = true;
}
return diff; return diff;
} }

View File

@ -21,6 +21,7 @@ namespace app {
bool layers : 1; bool layers : 1;
bool cels : 1; bool cels : 1;
bool images : 1; bool images : 1;
bool colorProfiles : 1;
DocDiff() : DocDiff() :
anything(false), anything(false),
@ -31,7 +32,8 @@ namespace app {
palettes(false), palettes(false),
layers(false), layers(false),
cels(false), cels(false),
images(false) { images(false),
colorProfiles(false) {
} }
}; };

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,6 +23,8 @@ namespace app {
// anything in the document could be changed. // anything in the document could be changed.
virtual void onGeneralUpdate(DocEvent& ev) { } virtual void onGeneralUpdate(DocEvent& ev) { }
virtual void onColorSpaceChanged(DocEvent& ev) { }
virtual void onPixelFormatChanged(DocEvent& ev) { } virtual void onPixelFormatChanged(DocEvent& ev) { }
virtual void onAddLayer(DocEvent& ev) { } virtual void onAddLayer(DocEvent& ev) { }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -113,6 +114,9 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
const frame_t firstFrame); const frame_t firstFrame);
static void ase_file_write_cel_extra_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, static void ase_file_write_cel_extra_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
const Cel* cel); const Cel* cel);
static void ase_file_write_color_profile(FILE* f,
dio::AsepriteFrameHeader* frame_header,
const doc::Sprite* sprite);
#if 0 #if 0
static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask); static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask);
#endif #endif
@ -265,6 +269,10 @@ bool AseFormat::onSave(FileOp* fop)
// Frame duration // Frame duration
frame_header.duration = sprite->frameDuration(frame); frame_header.duration = sprite->frameDuration(frame);
// Save color profile in first frame
if (outputFrame == 0)
ase_file_write_color_profile(f, &frame_header, sprite);
// is the first frame or did the palette change? // is the first frame or did the palette change?
Palette* pal = sprite->palette(frame); Palette* pal = sprite->palette(frame);
int palFrom = 0, palTo = pal->size()-1; int palFrom = 0, palTo = pal->size()-1;
@ -833,6 +841,50 @@ static void ase_file_write_cel_extra_chunk(FILE* f,
ase_file_write_padding(f, 16); ase_file_write_padding(f, 16);
} }
static void ase_file_write_color_profile(FILE* f,
dio::AsepriteFrameHeader* frame_header,
const doc::Sprite* sprite)
{
const gfx::ColorSpacePtr& cs = sprite->colorSpace();
if (!cs) // No color
return;
int type = ASE_FILE_NO_COLOR_PROFILE;
switch (cs->type()) {
case gfx::ColorSpace::None:
return; // Without color profile, don't write this chunk.
case gfx::ColorSpace::sRGB:
type = ASE_FILE_SRGB_COLOR_PROFILE;
break;
case gfx::ColorSpace::ICC:
type = ASE_FILE_ICC_COLOR_PROFILE;
break;
default:
ASSERT(false); // Unknown color profile
return;
}
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_COLOR_PROFILE);
fputw(type, f);
fputw(cs->hasGamma() ? ASE_COLOR_PROFILE_FLAG_GAMMA: 0, f);
fixmath::fixed gamma = 0;
if (cs->hasGamma())
gamma = fixmath::ftofix(cs->gamma());
fputl(gamma, f);
ase_file_write_padding(f, 8);
if (cs->type() == gfx::ColorSpace::ICC) {
const size_t size = cs->iccSize();
const void* data = cs->iccData();
fputl(size, f);
if (size && data)
fwrite(data, 1, size, f);
}
}
#if 0 #if 0
static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask) static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask)
{ {

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -8,6 +9,7 @@
#include "config.h" #include "config.h"
#endif #endif
#include "app/color_spaces.h"
#include "app/console.h" #include "app/console.h"
#include "app/context.h" #include "app/context.h"
#include "app/doc.h" #include "app/doc.h"
@ -280,6 +282,9 @@ public:
if (m_layer && m_opaque) if (m_layer && m_opaque)
m_layer->configureAsBackground(); m_layer->configureAsBackground();
// sRGB is the default color space for GIF files
m_sprite->setColorSpace(gfx::ColorSpace::MakeSRGB());
return true; return true;
} }
else else
@ -870,6 +875,7 @@ public:
GifEncoder(FileOp* fop, GifFileType* gifFile) GifEncoder(FileOp* fop, GifFileType* gifFile)
: m_fop(fop) : m_fop(fop)
, m_gifFile(gifFile) , m_gifFile(gifFile)
, m_document(fop->document())
, m_sprite(fop->document()->sprite()) , m_sprite(fop->document()->sprite())
, m_spriteBounds(m_sprite->bounds()) , m_spriteBounds(m_sprite->bounds())
, m_hasBackground(m_sprite->backgroundLayer() ? true: false) , m_hasBackground(m_sprite->backgroundLayer() ? true: false)
@ -1334,10 +1340,14 @@ private:
private: private:
static ColorMapObject* createColorMap(const Palette* palette) { ColorMapObject* createColorMap(const Palette* palette) {
int n = 1 << GifBitSizeLimited(palette->size()); int n = 1 << GifBitSizeLimited(palette->size());
ColorMapObject* colormap = GifMakeMapObject(n, nullptr); ColorMapObject* colormap = GifMakeMapObject(n, nullptr);
// Color space conversions
ConvertCS convert = convert_from_custom_to_srgb(
m_document->osColorSpace());
for (int i=0; i<n; ++i) { for (int i=0; i<n; ++i) {
color_t color; color_t color;
if (i < palette->size()) if (i < palette->size())
@ -1345,6 +1355,8 @@ private:
else else
color = rgba(0, 0, 0, 255); color = rgba(0, 0, 0, 255);
color = convert(color);
colormap->Colors[i].Red = rgba_getr(color); colormap->Colors[i].Red = rgba_getr(color);
colormap->Colors[i].Green = rgba_getg(color); colormap->Colors[i].Green = rgba_getg(color);
colormap->Colors[i].Blue = rgba_getb(color); colormap->Colors[i].Blue = rgba_getb(color);
@ -1355,6 +1367,7 @@ private:
FileOp* m_fop; FileOp* m_fop;
GifFileType* m_gifFile; GifFileType* m_gifFile;
const Doc* m_document;
const Sprite* m_sprite; const Sprite* m_sprite;
gfx::Rect m_spriteBounds; gfx::Rect m_spriteBounds;
bool m_hasBackground; bool m_hasBackground;

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,6 +23,7 @@
#include "base/memory.h" #include "base/memory.h"
#include "doc/doc.h" #include "doc/doc.h"
#include <algorithm>
#include <csetjmp> #include <csetjmp>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
@ -66,8 +68,11 @@ class JpegFormat : public FileFormat {
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
gfx::ColorSpacePtr loadColorSpace(FileOp* fop, jpeg_decompress_struct* dinfo);
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override; bool onSave(FileOp* fop) override;
void saveColorSpace(FileOp* fop, jpeg_compress_struct* cinfo,
const gfx::ColorSpace* colorSpace);
#endif #endif
base::SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) override; base::SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) override;
@ -107,9 +112,27 @@ static void output_message(j_common_ptr cinfo)
((struct error_mgr *)cinfo->err)->fop->setError("%s\n", buffer); ((struct error_mgr *)cinfo->err)->fop->setError("%s\n", buffer);
} }
// Some code to read color spaces from jpeg files is from Skia
// (SkJpegCodec.cpp) by Google Inc.
static constexpr uint32_t kMarkerMaxSize = 65533;
static constexpr uint32_t kICCMarker = JPEG_APP0 + 2;
static constexpr uint32_t kICCMarkerHeaderSize = 14;
static constexpr uint32_t kICCAvailDataPerMarker = (kMarkerMaxSize - kICCMarkerHeaderSize);
static constexpr uint8_t kICCSig[] = { 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0' };
static bool is_icc_marker(jpeg_marker_struct* marker)
{
if (kICCMarker != marker->marker ||
marker->data_length < kICCMarkerHeaderSize) {
return false;
}
else
return !memcmp(marker->data, kICCSig, sizeof(kICCSig));
}
bool JpegFormat::onLoad(FileOp* fop) bool JpegFormat::onLoad(FileOp* fop)
{ {
struct jpeg_decompress_struct cinfo; struct jpeg_decompress_struct dinfo;
struct error_mgr jerr; struct error_mgr jerr;
JDIMENSION num_scanlines; JDIMENSION num_scanlines;
JSAMPARRAY buffer; JSAMPARRAY buffer;
@ -121,60 +144,65 @@ bool JpegFormat::onLoad(FileOp* fop)
// Initialize the JPEG decompression object with error handling. // Initialize the JPEG decompression object with error handling.
jerr.fop = fop; jerr.fop = fop;
cinfo.err = jpeg_std_error(&jerr.head); dinfo.err = jpeg_std_error(&jerr.head);
jerr.head.error_exit = error_exit; jerr.head.error_exit = error_exit;
jerr.head.output_message = output_message; jerr.head.output_message = output_message;
// Establish the setjmp return context for error_exit to use. // Establish the setjmp return context for error_exit to use.
if (setjmp(jerr.setjmp_buffer)) { if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&dinfo);
return false; return false;
} }
jpeg_create_decompress(&cinfo); jpeg_create_decompress(&dinfo);
// Specify data source for decompression. // Specify data source for decompression.
jpeg_stdio_src(&cinfo, file); jpeg_stdio_src(&dinfo, file);
// Instruct jpeg library to save the markers that we care
// about. Since the color profile will not change, we can skip this
// step on rewinds.
jpeg_save_markers(&dinfo, kICCMarker, 0xFFFF);
// Read file header, set default decompression parameters. // Read file header, set default decompression parameters.
jpeg_read_header(&cinfo, true); jpeg_read_header(&dinfo, true);
if (cinfo.jpeg_color_space == JCS_GRAYSCALE) if (dinfo.jpeg_color_space == JCS_GRAYSCALE)
cinfo.out_color_space = JCS_GRAYSCALE; dinfo.out_color_space = JCS_GRAYSCALE;
else else
cinfo.out_color_space = JCS_RGB; dinfo.out_color_space = JCS_RGB;
// Start decompressor. // Start decompressor.
jpeg_start_decompress(&cinfo); jpeg_start_decompress(&dinfo);
// Create the image. // Create the image.
Image* image = fop->sequenceImage( Image* image = fop->sequenceImage(
(cinfo.out_color_space == JCS_RGB ? IMAGE_RGB: (dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
IMAGE_GRAYSCALE), IMAGE_GRAYSCALE),
cinfo.output_width, dinfo.output_width,
cinfo.output_height); dinfo.output_height);
if (!image) { if (!image) {
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&dinfo);
return false; return false;
} }
// Create the buffer. // Create the buffer.
buffer_height = cinfo.rec_outbuf_height; buffer_height = dinfo.rec_outbuf_height;
buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height); buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
if (!buffer) { if (!buffer) {
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&dinfo);
return false; return false;
} }
for (c=0; c<(int)buffer_height; c++) { for (c=0; c<(int)buffer_height; c++) {
buffer[c] = (JSAMPROW)base_malloc(sizeof(JSAMPLE) * buffer[c] = (JSAMPROW)base_malloc(sizeof(JSAMPLE) *
cinfo.output_width * cinfo.output_components); dinfo.output_width * dinfo.output_components);
if (!buffer[c]) { if (!buffer[c]) {
for (c--; c>=0; c--) for (c--; c>=0; c--)
base_free(buffer[c]); base_free(buffer[c]);
base_free(buffer); base_free(buffer);
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&dinfo);
return false; return false;
} }
} }
@ -185,12 +213,8 @@ bool JpegFormat::onLoad(FileOp* fop)
fop->sequenceSetColor(c, c, c, c); fop->sequenceSetColor(c, c, c, c);
// Read each scan line. // Read each scan line.
while (cinfo.output_scanline < cinfo.output_height) { while (dinfo.output_scanline < dinfo.output_height) {
// TODO num_scanlines = jpeg_read_scanlines(&dinfo, buffer, buffer_height);
/* if (plugin_want_close()) */
/* break; */
num_scanlines = jpeg_read_scanlines(&cinfo, buffer, buffer_height);
// RGB // RGB
if (image->pixelFormat() == IMAGE_RGB) { if (image->pixelFormat() == IMAGE_RGB) {
@ -200,7 +224,7 @@ bool JpegFormat::onLoad(FileOp* fop)
for (y=0; y<(int)num_scanlines; y++) { for (y=0; y<(int)num_scanlines; y++) {
src_address = ((uint8_t**)buffer)[y]; src_address = ((uint8_t**)buffer)[y];
dst_address = (uint32_t*)image->getPixelAddress(0, cinfo.output_scanline-1+y); dst_address = (uint32_t*)image->getPixelAddress(0, dinfo.output_scanline-1+y);
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
r = *(src_address++); r = *(src_address++);
@ -218,30 +242,107 @@ bool JpegFormat::onLoad(FileOp* fop)
for (y=0; y<(int)num_scanlines; y++) { for (y=0; y<(int)num_scanlines; y++) {
src_address = ((uint8_t**)buffer)[y]; src_address = ((uint8_t**)buffer)[y];
dst_address = (uint16_t*)image->getPixelAddress(0, cinfo.output_scanline-1+y); dst_address = (uint16_t*)image->getPixelAddress(0, dinfo.output_scanline-1+y);
for (x=0; x<image->width(); x++) for (x=0; x<image->width(); x++)
*(dst_address++) = graya(*(src_address++), 255); *(dst_address++) = graya(*(src_address++), 255);
} }
} }
fop->setProgress((float)(cinfo.output_scanline+1) / (float)(cinfo.output_height)); fop->setProgress((float)(dinfo.output_scanline+1) / (float)(dinfo.output_height));
if (fop->isStop()) if (fop->isStop())
break; break;
} }
/* destroy all data */ // Read color space
gfx::ColorSpacePtr colorSpace = loadColorSpace(fop, &dinfo);
if (colorSpace &&
fop->document()->sprite()->colorSpace()->type() == gfx::ColorSpace::None) {
fop->document()->sprite()->setColorSpace(colorSpace);
fop->document()->notifyColorSpaceChanged();
}
for (c=0; c<(int)buffer_height; c++) for (c=0; c<(int)buffer_height; c++)
base_free(buffer[c]); base_free(buffer[c]);
base_free(buffer); base_free(buffer);
jpeg_finish_decompress(&cinfo); jpeg_finish_decompress(&dinfo);
jpeg_destroy_decompress(&cinfo); jpeg_destroy_decompress(&dinfo);
return true; return true;
} }
// ICC profiles may be stored using a sequence of multiple markers. We obtain the ICC profile
// in two steps:
// (1) Discover all ICC profile markers and verify that they are numbered properly.
// (2) Copy the data from each marker into a contiguous ICC profile.
gfx::ColorSpacePtr JpegFormat::loadColorSpace(FileOp* fop, jpeg_decompress_struct* dinfo)
{
// Note that 256 will be enough storage space since each markerIndex is stored in 8-bits.
jpeg_marker_struct* markerSequence[256];
memset(markerSequence, 0, sizeof(markerSequence));
uint8_t numMarkers = 0;
size_t totalBytes = 0;
// Discover any ICC markers and verify that they are numbered properly.
for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) {
if (is_icc_marker(marker)) {
// Verify that numMarkers is valid and consistent.
if (0 == numMarkers) {
numMarkers = marker->data[13];
if (0 == numMarkers) {
fop->setError("ICC Profile Error: numMarkers must be greater than zero.\n");
return nullptr;
}
}
else if (numMarkers != marker->data[13]) {
fop->setError("ICC Profile Error: numMarkers must be consistent.\n");
return nullptr;
}
// Verify that the markerIndex is valid and unique. Note that zero is not
// a valid index.
uint8_t markerIndex = marker->data[12];
if (markerIndex == 0 || markerIndex > numMarkers) {
fop->setError("ICC Profile Error: markerIndex is invalid.\n");
return nullptr;
}
if (markerSequence[markerIndex]) {
fop->setError("ICC Profile Error: Duplicate value of markerIndex.\n");
return nullptr;
}
markerSequence[markerIndex] = marker;
ASSERT(marker->data_length >= kICCMarkerHeaderSize);
totalBytes += marker->data_length - kICCMarkerHeaderSize;
}
}
if (0 == totalBytes) {
// No non-empty ICC profile markers were found.
return nullptr;
}
// Combine the ICC marker data into a contiguous profile.
std::vector<uint8_t> iccData(totalBytes);
uint8_t* dst = &iccData[0];
for (uint32_t i = 1; i <= numMarkers; i++) {
jpeg_marker_struct* marker = markerSequence[i];
if (!marker) {
fop->setError("ICC Profile Error: Missing marker %d of %d.\n", i, numMarkers);
return nullptr;
}
uint8_t* src = ((uint8_t*)marker->data) + kICCMarkerHeaderSize;
size_t bytes = marker->data_length - kICCMarkerHeaderSize;
memcpy(dst, src, bytes);
dst = dst + bytes;
}
return gfx::ColorSpace::MakeICC(std::move(iccData));
}
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool JpegFormat::onSave(FileOp* fop) bool JpegFormat::onSave(FileOp* fop)
{ {
struct jpeg_compress_struct cinfo; struct jpeg_compress_struct cinfo;
@ -288,6 +389,10 @@ bool JpegFormat::onSave(FileOp* fop)
// START compressor. // START compressor.
jpeg_start_compress(&cinfo, true); jpeg_start_compress(&cinfo, true);
// Save color space
if (fop->document()->sprite()->colorSpace())
saveColorSpace(fop, &cinfo, fop->document()->sprite()->colorSpace().get());
// CREATE the buffer. // CREATE the buffer.
buffer_height = 1; buffer_height = 1;
buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height); buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
@ -360,7 +465,53 @@ bool JpegFormat::onSave(FileOp* fop)
// All fine. // All fine.
return true; return true;
} }
#endif
void JpegFormat::saveColorSpace(FileOp* fop, jpeg_compress_struct* cinfo,
const gfx::ColorSpace* colorSpace)
{
if (!colorSpace || colorSpace->type() != gfx::ColorSpace::ICC)
return;
size_t iccSize = colorSpace->iccSize();
auto iccData = (const uint8_t*)colorSpace->iccData();
if (!iccSize || !iccData)
return;
std::vector<uint8_t> markerData(kMarkerMaxSize);
int markerIndex = 1;
int numMarkers =
(iccSize / kICCAvailDataPerMarker) +
(iccSize % kICCAvailDataPerMarker > 0 ? 1: 0);
// ICC profile too big to fit in JPEG markers (64kb*255 ~= 16mb)
if (numMarkers > 255) {
fop->setError("ICC profile is too big to enter in the JPEG file.\n");
return;
}
while (iccSize > 0) {
const size_t n = std::min<int>(iccSize, kICCAvailDataPerMarker);
ASSERT(n > 0);
ASSERT(n < kICCAvailDataPerMarker);
// Marker Header
std::copy(kICCSig, kICCSig+sizeof(kICCSig), &markerData[0]);
markerData[sizeof(kICCSig) ] = markerIndex;
markerData[sizeof(kICCSig)+1] = numMarkers;
// Marker Data
std::copy(iccData, iccData+n, &markerData[kICCMarkerHeaderSize]);
jpeg_write_marker(cinfo, kICCMarker, &markerData[0], kICCMarkerHeaderSize + n);
++markerIndex;
iccSize -= n;
iccData += n;
}
}
#endif // ENABLE_SAVE
// Shows the JPEG configuration dialog. // Shows the JPEG configuration dialog.
base::SharedPtr<FormatOptions> JpegFormat::onGetFormatOptions(FileOp* fop) base::SharedPtr<FormatOptions> JpegFormat::onGetFormatOptions(FileOp* fop)

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -16,6 +17,7 @@
#include "app/file/png_format.h" #include "app/file/png_format.h"
#include "base/file_handle.h" #include "base/file_handle.h"
#include "doc/doc.h" #include "doc/doc.h"
#include "gfx/color_space.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -53,8 +55,10 @@ class PngFormat : public FileFormat {
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
gfx::ColorSpacePtr loadColorSpace(png_structp png_ptr, png_infop info_ptr);
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override; bool onSave(FileOp* fop) override;
void saveColorSpace(png_structp png_ptr, png_infop info_ptr, const gfx::ColorSpace* colorSpace);
#endif #endif
}; };
@ -68,7 +72,7 @@ static void report_png_error(png_structp png_ptr, png_const_charp error)
((FileOp*)png_get_error_ptr(png_ptr))->setError("libpng: %s\n", error); ((FileOp*)png_get_error_ptr(png_ptr))->setError("libpng: %s\n", error);
} }
// TODO this should be part of an png encoder instance // TODO this should be information in FileOp parameter of onSave()
static bool fix_one_alpha_pixel = false; static bool fix_one_alpha_pixel = false;
PngEncoderOneAlphaPixel::PngEncoderOneAlphaPixel(bool state) PngEncoderOneAlphaPixel::PngEncoderOneAlphaPixel(bool state)
@ -81,12 +85,26 @@ PngEncoderOneAlphaPixel::~PngEncoderOneAlphaPixel()
fix_one_alpha_pixel = false; fix_one_alpha_pixel = false;
} }
// As in png_fixed_point_to_float() in skia/src/codec/SkPngCodec.cpp
static float png_fixtof(png_fixed_point x)
{
// We multiply by the same factor that libpng used to convert
// fixed point -> double. Since we want floats, we choose to
// do the conversion ourselves rather than convert
// fixed point -> double -> float.
return ((float)x) * 0.00001f;
}
static png_fixed_point png_ftofix(float x)
{
return x * 100000.0f;
}
bool PngFormat::onLoad(FileOp* fop) bool PngFormat::onLoad(FileOp* fop)
{ {
png_uint_32 width, height, y; png_uint_32 width, height, y;
unsigned int sig_read = 0; unsigned int sig_read = 0;
png_structp png_ptr; png_structp png_ptr;
png_infop info_ptr;
int bit_depth, color_type, interlace_type; int bit_depth, color_type, interlace_type;
int num_palette; int num_palette;
png_colorp palette; png_colorp palette;
@ -110,7 +128,7 @@ bool PngFormat::onLoad(FileOp* fop)
} }
/* Allocate/initialize the memory for image information. */ /* Allocate/initialize the memory for image information. */
info_ptr = png_create_info_struct(png_ptr); png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) { if (info_ptr == NULL) {
fop->setError("png_create_info_struct\n"); fop->setError("png_create_info_struct\n");
png_destroy_read_struct(&png_ptr, NULL, NULL); png_destroy_read_struct(&png_ptr, NULL, NULL);
@ -126,11 +144,6 @@ bool PngFormat::onLoad(FileOp* fop)
return false; return false;
} }
// Do not check sRGB profile
#ifdef PNG_SKIP_sRGB_CHECK_PROFILE
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, 1);
#endif
/* Set up the input control if you are using standard C streams */ /* Set up the input control if you are using standard C streams */
png_init_io(png_ptr, fp); png_init_io(png_ptr, fp);
@ -357,12 +370,92 @@ bool PngFormat::onLoad(FileOp* fop)
} }
png_free(png_ptr, rows_pointer); png_free(png_ptr, rows_pointer);
// Setup the color space.
auto colorSpace = PngFormat::loadColorSpace(png_ptr, info_ptr);
if (colorSpace &&
fop->document()->sprite()->colorSpace()->type() == gfx::ColorSpace::None) {
fop->document()->sprite()->setColorSpace(colorSpace);
fop->document()->notifyColorSpaceChanged();
}
// Clean up after the read, and free any memory allocated // Clean up after the read, and free any memory allocated
png_destroy_read_struct(&png_ptr, &info_ptr, NULL); png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return true; return true;
} }
// Returns a colorSpace object that represents any
// color space information in the encoded data. If the encoded data
// contains an invalid/unsupported color space, this will return
// NULL. If there is no color space information, it will guess sRGB
//
// Code to read color spaces from png files from Skia (SkPngCodec.cpp)
// by Google Inc.
gfx::ColorSpacePtr PngFormat::loadColorSpace(png_structp png_ptr, png_infop info_ptr)
{
// First check for an ICC profile
png_bytep profile;
png_uint_32 length;
// The below variables are unused, however, we need to pass them in anyway or
// png_get_iCCP() will return nothing.
// Could knowing the |name| of the profile ever be interesting? Maybe for debugging?
png_charp name;
// The |compression| is uninteresting since:
// (1) libpng has already decompressed the profile for us.
// (2) "deflate" is the only mode of decompression that libpng supports.
int compression;
if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr,
&name, &compression,
&profile, &length)) {
auto colorSpace = gfx::ColorSpace::MakeICC(profile, length);
if (name)
colorSpace->setName(name);
return colorSpace;
}
// Second, check for sRGB.
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) {
// sRGB chunks also store a rendering intent: Absolute, Relative,
// Perceptual, and Saturation.
return gfx::ColorSpace::MakeSRGB();
}
// Next, check for chromaticities.
png_fixed_point wx, wy, rx, ry, gx, gy, bx, by, invGamma;
if (png_get_cHRM_fixed(png_ptr, info_ptr,
&wx, &wy, &rx, &ry, &gx, &gy, &bx, &by)) {
gfx::ColorSpacePrimaries primaries;
primaries.wx = png_fixtof(wx); primaries.wy = png_fixtof(wy);
primaries.rx = png_fixtof(rx); primaries.ry = png_fixtof(ry);
primaries.gx = png_fixtof(gx); primaries.gy = png_fixtof(gy);
primaries.bx = png_fixtof(bx); primaries.by = png_fixtof(by);
if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &invGamma)) {
gfx::ColorSpaceTransferFn fn;
fn.a = 1.0f;
fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
fn.g = 1.0f / png_fixtof(invGamma);
return gfx::ColorSpace::MakeRGB(fn, primaries);
}
// Default to sRGB gamma if the image has color space information,
// but does not specify gamma.
return gfx::ColorSpace::MakeRGBWithSRGBGamma(primaries);
}
// Last, check for gamma.
if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &invGamma)) {
// Since there is no cHRM, we will guess sRGB gamut.
return gfx::ColorSpace::MakeSRGBWithGamma(1.0f / png_fixtof(invGamma));
}
// Report that there is no color space information in the PNG.
// Guess sRGB in this case.
return gfx::ColorSpace::MakeSRGB();
}
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool PngFormat::onSave(FileOp* fop) bool PngFormat::onSave(FileOp* fop)
{ {
png_structp png_ptr; png_structp png_ptr;
@ -423,6 +516,9 @@ bool PngFormat::onSave(FileOp* fop)
png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type, png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (fop->document()->sprite()->colorSpace())
saveColorSpace(png_ptr, info_ptr, fop->document()->sprite()->colorSpace().get());
if (color_type == PNG_COLOR_TYPE_PALETTE) { if (color_type == PNG_COLOR_TYPE_PALETTE) {
int c, r, g, b; int c, r, g, b;
int pal_size = fop->sequenceGetNColors(); int pal_size = fop->sequenceGetNColors();
@ -592,6 +688,53 @@ bool PngFormat::onSave(FileOp* fop)
png_destroy_write_struct(&png_ptr, &info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr);
return true; return true;
} }
#endif
void PngFormat::saveColorSpace(png_structp png_ptr, png_infop info_ptr,
const gfx::ColorSpace* colorSpace)
{
switch (colorSpace->type()) {
case gfx::ColorSpace::None:
// Do just nothing (png file without profile, like old Aseprite versions)
break;
case gfx::ColorSpace::sRGB:
// TODO save the original intent
if (!colorSpace->hasGamma()) {
png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
return;
}
// Continue to RGB case...
case gfx::ColorSpace::RGB: {
if (colorSpace->hasPrimaries()) {
const gfx::ColorSpacePrimaries* p = colorSpace->primaries();
png_set_cHRM_fixed(png_ptr, info_ptr,
png_ftofix(p->wx), png_ftofix(p->wy),
png_ftofix(p->rx), png_ftofix(p->ry),
png_ftofix(p->gx), png_ftofix(p->gy),
png_ftofix(p->bx), png_ftofix(p->by));
}
if (colorSpace->hasGamma()) {
png_set_gAMA_fixed(png_ptr, info_ptr,
png_ftofix(1.0f / colorSpace->gamma()));
}
break;
}
case gfx::ColorSpace::ICC: {
png_set_iCCP(png_ptr, info_ptr,
(png_const_charp)colorSpace->name().c_str(),
PNG_COMPRESSION_TYPE_DEFAULT,
(png_const_bytep)colorSpace->iccData(),
(png_uint_32)colorSpace->iccSize());
break;
}
}
}
#endif // ENABLE_SAVE
} // namespace app } // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -11,6 +12,7 @@
#include "app/modules/gfx.h" #include "app/modules/gfx.h"
#include "app/app.h" #include "app/app.h"
#include "app/color_spaces.h"
#include "app/color_utils.h" #include "app/color_utils.h"
#include "app/console.h" #include "app/console.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
@ -81,8 +83,10 @@ void draw_color(ui::Graphics* g,
return; return;
app::Color color = _color; app::Color color = _color;
const int alpha = color.getAlpha();
int alpha = color.getAlpha(); // Color space conversion
auto convertColor = convert_from_current_to_screen_color_space();
if (alpha < 255) { if (alpha < 255) {
if (rc.w == rc.h) if (rc.w == rc.h)
@ -102,7 +106,7 @@ void draw_color(ui::Graphics* g,
int index = color.getIndex(); int index = color.getIndex();
if (index >= 0 && index < get_current_palette()->size()) { if (index >= 0 && index < get_current_palette()->size()) {
g->fillRect(color_utils::color_for_ui(color), rc); g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
} }
else { else {
g->fillRect(gfx::rgba(0, 0, 0), rc); g->fillRect(gfx::rgba(0, 0, 0), rc);
@ -112,7 +116,7 @@ void draw_color(ui::Graphics* g,
} }
} }
else { else {
g->fillRect(color_utils::color_for_ui(color), rc); g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
} }
} }
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello // Copyright (C) 2016-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -12,6 +13,8 @@
#include "app/ui/color_selector.h" #include "app/ui/color_selector.h"
#include "app/app.h"
#include "app/color_spaces.h"
#include "app/color_utils.h" #include "app/color_utils.h"
#include "app/modules/gfx.h" #include "app/modules/gfx.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
@ -99,14 +102,17 @@ public:
os::Surface* getCanvas(int w, int h, gfx::Color bgColor) { os::Surface* getCanvas(int w, int h, gfx::Color bgColor) {
assert_ui_thread(); assert_ui_thread();
auto activeCS = get_current_color_space();
if (!m_canvas || if (!m_canvas ||
m_canvas->width() != w || m_canvas->width() != w ||
m_canvas->height() != h) { m_canvas->height() != h ||
m_canvas->colorSpace() != activeCS) {
std::unique_lock<std::mutex> lock(m_mutex); std::unique_lock<std::mutex> lock(m_mutex);
stopCurrentPainting(lock); stopCurrentPainting(lock);
auto oldCanvas = m_canvas; auto oldCanvas = m_canvas;
m_canvas = os::instance()->createSurface(w, h); m_canvas = os::instance()->createSurface(w, h, activeCS);
m_canvas->fillRect(bgColor, gfx::Rect(0, 0, w, h)); m_canvas->fillRect(bgColor, gfx::Rect(0, 0, w, h));
if (oldCanvas) { if (oldCanvas) {
m_canvas->drawSurface(oldCanvas, 0, 0); m_canvas->drawSurface(oldCanvas, 0, 0);
@ -221,6 +227,10 @@ ColorSelector::ColorSelector()
{ {
initTheme(); initTheme();
painter.addRef(); painter.addRef();
m_appConn = App::instance()
->ColorSpaceChange.connect(
&ColorSelector::updateColorSpace, this);
} }
ColorSelector::~ColorSelector() ColorSelector::~ColorSelector()
@ -499,4 +509,10 @@ gfx::Rect ColorSelector::alphaBarBounds() const
return gfx::Rect(); return gfx::Rect();
} }
void ColorSelector::updateColorSpace()
{
m_paintFlags |= AllAreasFlag;
invalidate();
}
} // namespace app } // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello // Copyright (C) 2016-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -10,6 +11,7 @@
#include "app/color.h" #include "app/color.h"
#include "app/ui/color_source.h" #include "app/ui/color_source.h"
#include "obs/connection.h"
#include "obs/signal.h" #include "obs/signal.h"
#include "os/surface.h" #include "os/surface.h"
#include "ui/mouse_buttons.h" #include "ui/mouse_buttons.h"
@ -91,6 +93,8 @@ namespace app {
gfx::Rect bottomBarBounds() const; gfx::Rect bottomBarBounds() const;
gfx::Rect alphaBarBounds() const; gfx::Rect alphaBarBounds() const;
void updateColorSpace();
// Internal flag used to lock the modification of m_color. // Internal flag used to lock the modification of m_color.
// E.g. When the user picks a color harmony, we don't want to // E.g. When the user picks a color harmony, we don't want to
// change the main color. // change the main color.
@ -104,6 +108,8 @@ namespace app {
bool m_capturedInAlpha; bool m_capturedInAlpha;
ui::Timer m_timer; ui::Timer m_timer;
obs::scoped_connection m_appConn;
}; };
} // namespace app } // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -56,6 +57,8 @@
#include "doc/doc.h" #include "doc/doc.h"
#include "doc/mask_boundaries.h" #include "doc/mask_boundaries.h"
#include "doc/slice.h" #include "doc/slice.h"
#include "os/color_space.h"
#include "os/display.h"
#include "os/surface.h" #include "os/surface.h"
#include "os/system.h" #include "os/system.h"
#include "ui/ui.h" #include "ui/ui.h"
@ -643,19 +646,27 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
if (rendered) { if (rendered) {
// Convert the render to a os::Surface // Convert the render to a os::Surface
static os::Surface* tmp = nullptr; // TODO move this to other centralized place static os::Surface* tmp = nullptr; // TODO move this to other centralized place
if (!tmp || tmp->width() < rc2.w || tmp->height() < rc2.h) {
if (!tmp ||
tmp->width() < rc2.w ||
tmp->height() < rc2.h ||
tmp->colorSpace() != m_document->osColorSpace()) {
const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0); const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0);
const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0); const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
if (tmp) if (tmp)
tmp->dispose(); tmp->dispose();
tmp = os::instance()->createSurface(maxw, maxh);
tmp = os::instance()->createSurface(
maxw, maxh, m_document->osColorSpace());
} }
if (tmp->nativeHandle()) { if (tmp->nativeHandle()) {
if (newEngine) if (newEngine)
tmp->clear(); // TODO why we need this? tmp->clear(); // TODO why we need this?
convert_image_to_surface(rendered.get(), m_sprite->palette(m_frame), convert_image_to_surface(rendered.get(), m_sprite->palette(m_frame),
tmp, 0, 0, 0, 0, rc2.w, rc2.h); tmp, 0, 0, 0, 0, rc2.w, rc2.h);
if (newEngine) { if (newEngine) {
g->drawSurface(tmp, gfx::Rect(0, 0, rc2.w, rc2.h), dest); g->drawSurface(tmp, gfx::Rect(0, 0, rc2.w, rc2.h), dest);
} }
@ -1920,6 +1931,13 @@ void Editor::onShowExtrasChange()
invalidate(); invalidate();
} }
void Editor::onColorSpaceChanged(DocEvent& ev)
{
// As the document has a new color space, we've to redraw the
// complete canvas again with the new color profile.
invalidate();
}
void Editor::onExposeSpritePixels(DocEvent& ev) void Editor::onExposeSpritePixels(DocEvent& ev)
{ {
if (m_state && ev.sprite() == m_sprite) if (m_state && ev.sprite() == m_sprite)

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -26,6 +27,7 @@
#include "filters/tiled_mode.h" #include "filters/tiled_mode.h"
#include "gfx/fwd.h" #include "gfx/fwd.h"
#include "obs/connection.h" #include "obs/connection.h"
#include "os/color_space.h"
#include "render/projection.h" #include "render/projection.h"
#include "render/zoom.h" #include "render/zoom.h"
#include "ui/base.h" #include "ui/base.h"
@ -284,6 +286,7 @@ namespace app {
void onShowExtrasChange(); void onShowExtrasChange();
// DocObserver impl // DocObserver impl
void onColorSpaceChanged(DocEvent& ev) override;
void onExposeSpritePixels(DocEvent& ev) override; void onExposeSpritePixels(DocEvent& ev) override;
void onSpritePixelRatioChanged(DocEvent& ev) override; void onSpritePixelRatioChanged(DocEvent& ev) override;
void onBeforeRemoveLayer(DocEvent& ev) override; void onBeforeRemoveLayer(DocEvent& ev) override;

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -21,6 +22,7 @@
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/util/clipboard.h" #include "app/util/clipboard.h"
#include "base/bind.h"
#include "base/convert_to.h" #include "base/convert_to.h"
#include "doc/image.h" #include "doc/image.h"
#include "doc/palette.h" #include "doc/palette.h"
@ -65,7 +67,9 @@ PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDeleg
setFocusStop(true); setFocusStop(true);
setDoubleBuffered(true); setDoubleBuffered(true);
m_conn = App::instance()->PaletteChange.connect(&PaletteView::onAppPaletteChange, this); m_palConn = App::instance()->PaletteChange.connect(&PaletteView::onAppPaletteChange, this);
m_csConn = App::instance()->ColorSpaceChange.connect(
base::Bind<void>(&PaletteView::invalidate, this));
InitTheme.connect( InitTheme.connect(
[this]{ [this]{

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -152,7 +153,8 @@ namespace app {
int m_rangeAnchor; int m_rangeAnchor;
doc::PalettePicks m_selectedEntries; doc::PalettePicks m_selectedEntries;
bool m_isUpdatingColumns; bool m_isUpdatingColumns;
obs::scoped_connection m_conn; obs::scoped_connection m_palConn;
obs::scoped_connection m_csConn;
Hit m_hot; Hit m_hot;
bool m_copy; bool m_copy;
}; };

View File

@ -1,3 +1,4 @@
Copyright (c) 2018 Igara Studio S.A.
Copyright (c) 2016-2018 David Capello Copyright (c) 2016-2018 David Capello
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining

View File

@ -1,4 +1,7 @@
# Aseprite Document IO Library # Aseprite Document IO Library
*Copyright (C) 2016-2018 David Capello*
> Distributed under [MIT license](LICENSE.txt) > Distributed under [MIT license](LICENSE.txt)
Library to decode `doc::Document` from `.aseprite` files. This
library should support encoding of `.aseprite` files in the near
future.

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library // Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -18,6 +19,7 @@
#define ASE_FILE_CHUNK_LAYER 0x2004 #define ASE_FILE_CHUNK_LAYER 0x2004
#define ASE_FILE_CHUNK_CEL 0x2005 #define ASE_FILE_CHUNK_CEL 0x2005
#define ASE_FILE_CHUNK_CEL_EXTRA 0x2006 #define ASE_FILE_CHUNK_CEL_EXTRA 0x2006
#define ASE_FILE_CHUNK_COLOR_PROFILE 0x2007
#define ASE_FILE_CHUNK_MASK 0x2016 #define ASE_FILE_CHUNK_MASK 0x2016
#define ASE_FILE_CHUNK_PATH 0x2017 #define ASE_FILE_CHUNK_PATH 0x2017
#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018 #define ASE_FILE_CHUNK_FRAME_TAGS 0x2018
@ -33,6 +35,12 @@
#define ASE_FILE_LINK_CEL 1 #define ASE_FILE_LINK_CEL 1
#define ASE_FILE_COMPRESSED_CEL 2 #define ASE_FILE_COMPRESSED_CEL 2
#define ASE_FILE_NO_COLOR_PROFILE 0
#define ASE_FILE_SRGB_COLOR_PROFILE 1
#define ASE_FILE_ICC_COLOR_PROFILE 2
#define ASE_COLOR_PROFILE_FLAG_GAMMA 1
#define ASE_PALETTE_FLAG_HAS_NAME 1 #define ASE_PALETTE_FLAG_HAS_NAME 1
#define ASE_USER_DATA_FLAG_HAS_TEXT 1 #define ASE_USER_DATA_FLAG_HAS_TEXT 1

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library // Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -14,6 +15,7 @@
#include "base/exception.h" #include "base/exception.h"
#include "base/file_handle.h" #include "base/file_handle.h"
#include "base/fs.h" #include "base/fs.h"
#include "gfx/color_space.h"
#include "dio/aseprite_common.h" #include "dio/aseprite_common.h"
#include "dio/decode_delegate.h" #include "dio/decode_delegate.h"
#include "dio/file_interface.h" #include "dio/file_interface.h"
@ -152,6 +154,11 @@ bool AsepriteDecoder::decode()
break; break;
} }
case ASE_FILE_CHUNK_COLOR_PROFILE: {
readColorProfile(sprite.get());
break;
}
case ASE_FILE_CHUNK_MASK: { case ASE_FILE_CHUNK_MASK: {
doc::Mask* mask = readMaskChunk(); doc::Mask* mask = readMaskChunk();
if (mask) if (mask)
@ -730,6 +737,46 @@ void AsepriteDecoder::readCelExtraChunk(doc::Cel* cel)
} }
} }
void AsepriteDecoder::readColorProfile(doc::Sprite* sprite)
{
int type = read16();
int flags = read16();
fixmath::fixed gamma = read32();
readPadding(8);
// Without color space, like old Aseprite versions
gfx::ColorSpacePtr cs(nullptr);
switch (type) {
case ASE_FILE_NO_COLOR_PROFILE:
if (flags & ASE_COLOR_PROFILE_FLAG_GAMMA)
cs = gfx::ColorSpace::MakeSRGBWithGamma(fixmath::fixtof(gamma));
else
cs = gfx::ColorSpace::MakeNone();
break;
case ASE_FILE_SRGB_COLOR_PROFILE:
if (flags & ASE_COLOR_PROFILE_FLAG_GAMMA)
cs = gfx::ColorSpace::MakeSRGBWithGamma(fixmath::fixtof(gamma));
else
cs = gfx::ColorSpace::MakeSRGB();
break;
case ASE_FILE_ICC_COLOR_PROFILE: {
size_t length = read32();
if (length > 0) {
std::vector<uint8_t> data(length);
readBytes(&data[0], length);
cs = gfx::ColorSpace::MakeICC(std::move(data));
}
break;
}
}
sprite->setColorSpace(cs);
}
doc::Mask* AsepriteDecoder::readMaskChunk() doc::Mask* AsepriteDecoder::readMaskChunk()
{ {
int c, u, v, byte; int c, u, v, byte;

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library // Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2017 David Capello // Copyright (c) 2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -52,6 +53,7 @@ private:
AsepriteHeader* header, AsepriteHeader* header,
size_t chunk_end); size_t chunk_end);
void readCelExtraChunk(doc::Cel* cel); void readCelExtraChunk(doc::Cel* cel);
void readColorProfile(doc::Sprite* sprite);
doc::Mask* readMaskChunk(); doc::Mask* readMaskChunk();
void readFrameTagsChunk(doc::FrameTags* frameTags); void readFrameTagsChunk(doc::FrameTags* frameTags);
void readSlicesChunk(doc::Slices& slices); void readSlicesChunk(doc::Slices& slices);

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library // Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2017 David Capello // Copyright (c) 2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -59,4 +60,9 @@ uint32_t Decoder::read32()
return 0; return 0;
} }
size_t Decoder::readBytes(uint8_t* buf, size_t n)
{
return m_f->readBytes(buf, n);
}
} // namespace dio } // namespace dio

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library // Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2017 David Capello // Copyright (c) 2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -9,6 +10,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <cstring>
namespace doc { namespace doc {
class Document; class Document;
@ -33,6 +35,7 @@ protected:
uint8_t read8(); uint8_t read8();
uint16_t read16(); uint16_t read16();
uint32_t read32(); uint32_t read32();
size_t readBytes(uint8_t* buf, size_t n);
private: private:
DecodeDelegate* m_delegate; DecodeDelegate* m_delegate;

View File

@ -1,3 +1,4 @@
Copyright (c) 2018 Igara Studio S.A.
Copyright (c) 2001-2018 David Capello Copyright (c) 2001-2018 David Capello
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining

View File

@ -1,4 +1,5 @@
# Aseprite Document Library # Aseprite Document Library
*Copyright (C) 2001-2018 David Capello*
> Distributed under [MIT license](LICENSE.txt) > Distributed under [MIT license](LICENSE.txt)
Library to represent the structure of a sprite on Aseprite.

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2016 David Capello // Copyright (c) 2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -11,6 +12,7 @@
#include "base/debug.h" #include "base/debug.h"
#include "doc/color.h" #include "doc/color.h"
#include "doc/color_mode.h" #include "doc/color_mode.h"
#include "gfx/color_space.h"
#include "gfx/rect.h" #include "gfx/rect.h"
#include "gfx/size.h" #include "gfx/size.h"
@ -25,7 +27,8 @@ namespace doc {
: m_colorMode(colorMode), : m_colorMode(colorMode),
m_width(width), m_width(width),
m_height(height), m_height(height),
m_maskColor(maskColor) { m_maskColor(maskColor),
m_colorSpace(gfx::ColorSpace::MakeNone()) {
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
} }
@ -35,6 +38,7 @@ namespace doc {
int height() const { return m_height; } int height() const { return m_height; }
gfx::Size size() const { return gfx::Size(m_width, m_height); } gfx::Size size() const { return gfx::Size(m_width, m_height); }
gfx::Rect bounds() const { return gfx::Rect(0, 0, m_width, m_height); } gfx::Rect bounds() const { return gfx::Rect(0, 0, m_width, m_height); }
const gfx::ColorSpacePtr& colorSpace() const { return m_colorSpace; }
// The transparent color for colored images (0 by default) or just 0 for RGBA and Grayscale // The transparent color for colored images (0 by default) or just 0 for RGBA and Grayscale
color_t maskColor() const { return m_maskColor; } color_t maskColor() const { return m_maskColor; }
@ -43,6 +47,7 @@ namespace doc {
void setWidth(const int width) { m_width = width; } void setWidth(const int width) { m_width = width; }
void setHeight(const int height) { m_height = height; } void setHeight(const int height) { m_height = height; }
void setMaskColor(const color_t color) { m_maskColor = color; } void setMaskColor(const color_t color) { m_maskColor = color; }
void setColorSpace(const gfx::ColorSpacePtr& cs) { m_colorSpace = cs; }
void setSize(const int width, const int height) { void setSize(const int width, const int height) {
m_width = width; m_width = width;
@ -59,6 +64,7 @@ namespace doc {
int m_width; int m_width;
int m_height; int m_height;
color_t m_maskColor; color_t m_maskColor;
gfx::ColorSpacePtr m_colorSpace;
}; };
} // namespace doc } // namespace doc

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -34,7 +35,7 @@ namespace doc {
Sprite::Sprite(PixelFormat format, int width, int height, int ncolors) Sprite::Sprite(PixelFormat format, int width, int height, int ncolors)
: Object(ObjectType::Sprite) : Object(ObjectType::Sprite)
, m_document(NULL) , m_document(nullptr)
, m_spec((ColorMode)format, width, height, 0) , m_spec((ColorMode)format, width, height, 0)
, m_pixelRatio(1, 1) , m_pixelRatio(1, 1)
, m_frames(1) , m_frames(1)
@ -149,6 +150,11 @@ void Sprite::setSize(int width, int height)
m_spec.setSize(width, height); m_spec.setSize(width, height);
} }
void Sprite::setColorSpace(const gfx::ColorSpacePtr& colorSpace)
{
m_spec.setColorSpace(colorSpace);
}
bool Sprite::needAlpha() const bool Sprite::needAlpha() const
{ {
switch (pixelFormat()) { switch (pixelFormat()) {

View File

@ -1,4 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -75,10 +76,12 @@ namespace doc {
gfx::Rect bounds() const { return m_spec.bounds(); } gfx::Rect bounds() const { return m_spec.bounds(); }
int width() const { return m_spec.width(); } int width() const { return m_spec.width(); }
int height() const { return m_spec.height(); } int height() const { return m_spec.height(); }
const gfx::ColorSpacePtr& colorSpace() const { return m_spec.colorSpace(); }
void setPixelFormat(PixelFormat format); void setPixelFormat(PixelFormat format);
void setPixelRatio(const PixelRatio& pixelRatio); void setPixelRatio(const PixelRatio& pixelRatio);
void setSize(int width, int height); void setSize(int width, int height);
void setColorSpace(const gfx::ColorSpacePtr& colorSpace);
// Returns true if the rendered images will contain alpha values less // Returns true if the rendered images will contain alpha values less
// than 255. Only RGBA and Grayscale images without background needs // than 255. Only RGBA and Grayscale images without background needs