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
[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`.
## Skia on Windows
@ -234,7 +234,7 @@ Then:
Just ignore it.)
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
python tools/git-sync-deps
@ -265,7 +265,7 @@ several minutes to finish:
mkdir $HOME/deps
cd $HOME/deps
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}"
cd skia
python tools/git-sync-deps
@ -290,7 +290,7 @@ several minutes to finish:
mkdir $HOME/deps
cd $HOME/deps
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}"
cd skia
python tools/git-sync-deps

View File

@ -1,5 +1,4 @@
# 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://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
* [David Capello](https://davidcapello.com/): Lead developer, bug fixing, new features, designer, and maintainer.
* [Gaspar Capello](https://github.com/Gasparoken): Developer, bug fixing.
[Igara Studio](https://www.igarastudio.com/) is developing Aseprite:
* [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

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2014-2018 by David Capello -->
<!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2014-2018 David Capello -->
<preferences>
<types>
@ -97,6 +98,13 @@
<value id="HSV" value="0" />
<value id="HSL" value="1" />
</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>
<global>
@ -301,6 +309,12 @@
<section id="scripts">
<option id="show_run_script_alert" type="bool" default="true" />
</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>
<tool>

View File

@ -1,5 +1,6 @@
# Aseprite
# Copyright (C) 2016-2018 by David Capello
# Copyright (C) 2018 Igara Studio S.A.
# Copyright (C) 2016-2018 David Capello
[advanced_mode]
title = Warning - Important
@ -565,6 +566,7 @@ dont_show_tooltip = <<<END
Check in case that you want to establish
the given option as the default option.
END
reset = Reset
[gif_options]
title = GIF Options
@ -861,6 +863,7 @@ title = Preferences
section_general = General
section_files = Files
section_alerts = Alerts
section_color = Color
section_editor = Editor
section_selection = Selection
section_timeline = Timeline
@ -994,7 +997,6 @@ bg_checked = Checked Background
bg_size = Size:
bg_apply_zoom = Apply Zoom
bg_colors = Colors:
reset_bg = Reset
grid_visible = Visible Grid
grid_x = X:
grid_y = Y:
@ -1006,7 +1008,6 @@ grid_auto = Auto
grid_pixel_grid_visible = Visible Pixel Grid
grid_pixel_grid_color = Color:
grid_pixel_grid_opacity = Opacity:
reset_grid = Reset
guides = Guides
slices = Slices
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
run_script_alert = Show alert when we try to run a script
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
select_theme = &Select
download_themes = Download Themes
@ -1189,6 +1199,9 @@ pixel_ratio = Pixel Aspect Ratio:
square_pixels = Square Pixels (1:1)
double_wide = Double-wide Pixels (2:1)
double_high = Double-high Pixels (1:2)
color_profile = Color Profile:
assign_color_profile = Assign
convert_color_profile = Convert
[sprite_size]
title = Sprite Size

View File

@ -1,5 +1,6 @@
<!-- Aseprite -->
<!-- Copyright (C) 2001-2018 by David Capello -->
<!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<window id="options" text="@.title">
<vbox>
@ -8,6 +9,7 @@
<listbox id="section_listbox">
<listitem text="@.section_general" value="section_general" />
<listitem text="@.section_files" value="section_files" />
<listitem text="@.section_color" value="section_color" />
<listitem text="@.section_alerts" value="section_alerts" />
<listitem text="@.section_editor" value="section_editor" />
<listitem text="@.section_selection" value="section_selection" />
@ -114,6 +116,38 @@
</grid>
</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 -->
<vbox id="section_editor">
<separator text="@.section_editor" horizontal="true" />
@ -188,7 +222,7 @@
</combobox>
<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_specific_color" value="1" />
</combobox>
@ -219,7 +253,7 @@
<hbox>
<hbox expansive="true" />
<button id="reset_bg" text="@.reset_bg" width="60" />
<button id="reset_bg" text="@general.reset" width="60" />
</hbox>
</vbox>
@ -269,7 +303,7 @@
<hbox>
<hbox expansive="true" />
<button id="reset_grid" text="@.reset_grid" width="60" />
<button id="reset_grid" text="@general.reset" width="60" />
</hbox>
</vbox>

View File

@ -1,5 +1,6 @@
<!-- Aseprite -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<!-- Copyright (C) 2018 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 David Capello -->
<gui>
<window id="sprite_properties" text="@.title">
<vbox>
@ -28,6 +29,15 @@
<listitem text="@.double_high" value="1:2" />
</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>
<separator horizontal="true" />
<hbox>

View File

@ -1,7 +1,5 @@
# Aseprite File Format (.ase/.aseprite) Specifications
> Copyright (C) 2001-2018 by David Capello
1. [References](#references)
2. [Introduction](#introduction)
3. [Header](#header)
@ -213,6 +211,26 @@ Adds extra information to the latest read cel.
FIXED Height of the cel in the sprite
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
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")
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)
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.
* [gen](gen/) (base): Helper utility to generate C++ files from different XMLs.
* [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
* [doc](doc/) (base, fixmath, gfx): 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.
* [doc](doc/) (base, fixmath, gfx, os): Document model library.
* [ui](ui/) (base, gfx, os): Portable UI library (buttons, windows, text fields, etc.)
* [updater](updater/) (base, cfg, net): Component to check for updates.
## Level 4
## Level 3
* [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
* [app](app/) (base, doc, dio, filters, fixmath, flic, gfx, pen, render, scripting, os, ui, undo, updater)
## Level 6
* [main](main/) (app, base, os, ui)
* [desktop](desktop/) (base, doc, dio, render): Integration with the desktop (Windows Explorer, Finder, GNOME, KDE, etc.)
# Debugging Tricks

View File

@ -1,4 +1,5 @@
# Aseprite
# Copyright (C) 2018 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# 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_palette.cpp
cmd/add_slice.cpp
cmd/assign_color_profile.cpp
cmd/background_from_layer.cpp
cmd/clear_cel.cpp
cmd/clear_image.cpp
cmd/clear_mask.cpp
cmd/clear_rect.cpp
cmd/configure_background.cpp
cmd/convert_color_profile.cpp
cmd/copy_cel.cpp
cmd/copy_frame.cpp
cmd/copy_rect.cpp
@ -500,6 +503,7 @@ add_library(app-lib
cmd_transaction.cpp
color.cpp
color_picker.cpp
color_spaces.cpp
color_utils.cpp
commands/cmd_background_from_layer.cpp
commands/cmd_cel_opacity.cpp

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -119,6 +120,7 @@ namespace app {
// App Signals
obs::signal<void()> Exit;
obs::signal<void()> PaletteChange;
obs::signal<void()> ColorSpaceChange;
private:
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
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// 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 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;
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 {
public:
ThemeItem(const std::string& path,
@ -153,6 +179,21 @@ public:
recentFiles()->setValue(m_pref.general.recentItems());
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
resetAlerts()->Click.connect(base::Bind<void>(&OptionsWindow::onResetAlerts, this));
@ -444,6 +485,14 @@ public:
m_pref.guides.autoGuidesColor(autoGuidesColor()->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->grid.bounds(gridBounds());
m_curPref->grid.color(gridColor()->getColor());
@ -627,6 +676,53 @@ private:
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() {
fileFormatDoesntSupportAlert()->resetWithDefaultValue();
exportAnimationInSequenceAlert()->resetWithDefaultValue();
@ -1124,6 +1220,7 @@ private:
std::string m_restoreThisTheme;
int m_restoreScreenScaling;
int m_restoreUIScaling;
std::vector<os::ColorSpacePtr> m_colorSpaces;
};
class OptionsCommand : public Command {

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -8,12 +9,15 @@
#include "config.h"
#endif
#include "app/cmd/assign_color_profile.h"
#include "app/cmd/convert_color_profile.h"
#include "app/cmd/set_pixel_ratio.h"
#include "app/color.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/doc_api.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/tx.h"
#include "app/ui/color_button.h"
#include "app/util/pixel_ratio.h"
@ -22,12 +26,13 @@
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "os/color_space.h"
#include "os/system.h"
#include "ui/ui.h"
#include "sprite_properties.xml.h"
#include <cstdio>
namespace app {
using namespace ui;
@ -56,11 +61,15 @@ bool SpritePropertiesCommand::onEnabled(Context* context)
void SpritePropertiesCommand::onExecute(Context* context)
{
std::string imgtype_text;
char buf[256];
ColorButton* color_button = NULL;
ColorButton* color_button = nullptr;
// List of available color profiles
std::vector<os::ColorSpacePtr> colorSpaces;
os::instance()->listColorSpaces(colorSpaces);
// Load the window widget
app::gen::SpriteProperties window;
int selectedColorProfile = -1;
// Get sprite properties and fill frame fields
{
@ -77,8 +86,8 @@ void SpritePropertiesCommand::onExecute(Context* context)
imgtype_text = "Grayscale";
break;
case IMAGE_INDEXED:
std::sprintf(buf, "Indexed (%d colors)", sprite->palette(0)->size());
imgtype_text = buf;
imgtype_text = fmt::format("Indexed ({0} colors)",
sprite->palette(0)->size());
break;
default:
ASSERT(false);
@ -116,6 +125,64 @@ void SpritePropertiesCommand::onExecute(Context* context)
// Pixel ratio
window.pixelRatio()->setValue(
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();

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -35,6 +36,7 @@
#include "doc/sprite.h"
#include "doc/string_io.h"
#include "doc/subobjects_io.h"
#include "fixmath/fixmath.h"
#include <fstream>
#include <map>
@ -298,9 +300,30 @@ private:
}
}
// Read color space
gfx::ColorSpacePtr colorSpace = readColorSpace(s);
if (colorSpace)
spr->setColorSpace(colorSpace);
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?
Layer* readLayer(std::ifstream& s) {
LayerFlags flags = (LayerFlags)read32(s);

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -33,6 +34,7 @@
#include "doc/slice_io.h"
#include "doc/sprite.h"
#include "doc/string_io.h"
#include "fixmath/fixmath.h"
#include <fstream>
#include <map>
@ -165,6 +167,23 @@ private:
for (const Slice* slice : spr->slices())
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;
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -31,6 +32,8 @@
#include "doc/mask_boundaries.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "os/display.h"
#include "os/system.h"
#include <limits>
#include <map>
@ -54,6 +57,8 @@ Doc::Doc(Sprite* sprite)
if (sprite)
sprites().add(sprite);
updateOSColorSpace(false);
}
Doc::~Doc()
@ -111,6 +116,15 @@ void Doc::notifyGeneralUpdate()
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)
{
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
gfx::Point Doc::NoLastDrawingPoint()
{

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// 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);
static void ase_file_write_cel_extra_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
const Cel* cel);
static void ase_file_write_color_profile(FILE* f,
dio::AsepriteFrameHeader* frame_header,
const doc::Sprite* sprite);
#if 0
static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask);
#endif
@ -265,6 +269,10 @@ bool AseFormat::onSave(FileOp* fop)
// Frame duration
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?
Palette* pal = sprite->palette(frame);
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);
}
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
static void ase_file_write_mask_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, Mask* mask)
{

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -22,6 +23,7 @@
#include "base/memory.h"
#include "doc/doc.h"
#include <algorithm>
#include <csetjmp>
#include <cstdio>
#include <cstdlib>
@ -66,8 +68,11 @@ class JpegFormat : public FileFormat {
}
bool onLoad(FileOp* fop) override;
gfx::ColorSpacePtr loadColorSpace(FileOp* fop, jpeg_decompress_struct* dinfo);
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
void saveColorSpace(FileOp* fop, jpeg_compress_struct* cinfo,
const gfx::ColorSpace* colorSpace);
#endif
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);
}
// 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)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_decompress_struct dinfo;
struct error_mgr jerr;
JDIMENSION num_scanlines;
JSAMPARRAY buffer;
@ -121,60 +144,65 @@ bool JpegFormat::onLoad(FileOp* fop)
// Initialize the JPEG decompression object with error handling.
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.output_message = output_message;
// Establish the setjmp return context for error_exit to use.
if (setjmp(jerr.setjmp_buffer)) {
jpeg_destroy_decompress(&cinfo);
jpeg_destroy_decompress(&dinfo);
return false;
}
jpeg_create_decompress(&cinfo);
jpeg_create_decompress(&dinfo);
// 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.
jpeg_read_header(&cinfo, true);
jpeg_read_header(&dinfo, true);
if (cinfo.jpeg_color_space == JCS_GRAYSCALE)
cinfo.out_color_space = JCS_GRAYSCALE;
if (dinfo.jpeg_color_space == JCS_GRAYSCALE)
dinfo.out_color_space = JCS_GRAYSCALE;
else
cinfo.out_color_space = JCS_RGB;
dinfo.out_color_space = JCS_RGB;
// Start decompressor.
jpeg_start_decompress(&cinfo);
jpeg_start_decompress(&dinfo);
// Create the image.
Image* image = fop->sequenceImage(
(cinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
IMAGE_GRAYSCALE),
cinfo.output_width,
cinfo.output_height);
dinfo.output_width,
dinfo.output_height);
if (!image) {
jpeg_destroy_decompress(&cinfo);
jpeg_destroy_decompress(&dinfo);
return false;
}
// Create the buffer.
buffer_height = cinfo.rec_outbuf_height;
buffer_height = dinfo.rec_outbuf_height;
buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
if (!buffer) {
jpeg_destroy_decompress(&cinfo);
jpeg_destroy_decompress(&dinfo);
return false;
}
for (c=0; c<(int)buffer_height; c++) {
buffer[c] = (JSAMPROW)base_malloc(sizeof(JSAMPLE) *
cinfo.output_width * cinfo.output_components);
dinfo.output_width * dinfo.output_components);
if (!buffer[c]) {
for (c--; c>=0; c--)
base_free(buffer[c]);
base_free(buffer);
jpeg_destroy_decompress(&cinfo);
jpeg_destroy_decompress(&dinfo);
return false;
}
}
@ -185,12 +213,8 @@ bool JpegFormat::onLoad(FileOp* fop)
fop->sequenceSetColor(c, c, c, c);
// Read each scan line.
while (cinfo.output_scanline < cinfo.output_height) {
// TODO
/* if (plugin_want_close()) */
/* break; */
num_scanlines = jpeg_read_scanlines(&cinfo, buffer, buffer_height);
while (dinfo.output_scanline < dinfo.output_height) {
num_scanlines = jpeg_read_scanlines(&dinfo, buffer, buffer_height);
// RGB
if (image->pixelFormat() == IMAGE_RGB) {
@ -200,7 +224,7 @@ bool JpegFormat::onLoad(FileOp* fop)
for (y=0; y<(int)num_scanlines; 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++) {
r = *(src_address++);
@ -218,30 +242,107 @@ bool JpegFormat::onLoad(FileOp* fop)
for (y=0; y<(int)num_scanlines; 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++)
*(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())
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++)
base_free(buffer[c]);
base_free(buffer);
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
jpeg_finish_decompress(&dinfo);
jpeg_destroy_decompress(&dinfo);
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
bool JpegFormat::onSave(FileOp* fop)
{
struct jpeg_compress_struct cinfo;
@ -288,6 +389,10 @@ bool JpegFormat::onSave(FileOp* fop)
// START compressor.
jpeg_start_compress(&cinfo, true);
// Save color space
if (fop->document()->sprite()->colorSpace())
saveColorSpace(fop, &cinfo, fop->document()->sprite()->colorSpace().get());
// CREATE the buffer.
buffer_height = 1;
buffer = (JSAMPARRAY)base_malloc(sizeof(JSAMPROW) * buffer_height);
@ -360,7 +465,53 @@ bool JpegFormat::onSave(FileOp* fop)
// All fine.
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.
base::SharedPtr<FormatOptions> JpegFormat::onGetFormatOptions(FileOp* fop)

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -16,6 +17,7 @@
#include "app/file/png_format.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "gfx/color_space.h"
#include <stdio.h>
#include <stdlib.h>
@ -53,8 +55,10 @@ class PngFormat : public FileFormat {
}
bool onLoad(FileOp* fop) override;
gfx::ColorSpacePtr loadColorSpace(png_structp png_ptr, png_infop info_ptr);
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
void saveColorSpace(png_structp png_ptr, png_infop info_ptr, const gfx::ColorSpace* colorSpace);
#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);
}
// 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;
PngEncoderOneAlphaPixel::PngEncoderOneAlphaPixel(bool state)
@ -81,12 +85,26 @@ PngEncoderOneAlphaPixel::~PngEncoderOneAlphaPixel()
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)
{
png_uint_32 width, height, y;
unsigned int sig_read = 0;
png_structp png_ptr;
png_infop info_ptr;
int bit_depth, color_type, interlace_type;
int num_palette;
png_colorp palette;
@ -110,7 +128,7 @@ bool PngFormat::onLoad(FileOp* fop)
}
/* 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) {
fop->setError("png_create_info_struct\n");
png_destroy_read_struct(&png_ptr, NULL, NULL);
@ -126,11 +144,6 @@ bool PngFormat::onLoad(FileOp* fop)
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 */
png_init_io(png_ptr, fp);
@ -357,12 +370,92 @@ bool PngFormat::onLoad(FileOp* fop)
}
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
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
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
bool PngFormat::onSave(FileOp* fop)
{
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_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) {
int c, r, g, b;
int pal_size = fop->sequenceGetNColors();
@ -592,6 +688,53 @@ bool PngFormat::onSave(FileOp* fop)
png_destroy_write_struct(&png_ptr, &info_ptr);
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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -56,6 +57,8 @@
#include "doc/doc.h"
#include "doc/mask_boundaries.h"
#include "doc/slice.h"
#include "os/color_space.h"
#include "os/display.h"
#include "os/surface.h"
#include "os/system.h"
#include "ui/ui.h"
@ -643,19 +646,27 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
if (rendered) {
// Convert the render to a os::Surface
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 maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
if (tmp)
tmp->dispose();
tmp = os::instance()->createSurface(maxw, maxh);
tmp = os::instance()->createSurface(
maxw, maxh, m_document->osColorSpace());
}
if (tmp->nativeHandle()) {
if (newEngine)
tmp->clear(); // TODO why we need this?
convert_image_to_surface(rendered.get(), m_sprite->palette(m_frame),
tmp, 0, 0, 0, 0, rc2.w, rc2.h);
if (newEngine) {
g->drawSurface(tmp, gfx::Rect(0, 0, rc2.w, rc2.h), dest);
}
@ -1920,6 +1931,13 @@ void Editor::onShowExtrasChange()
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)
{
if (m_state && ev.sprite() == m_sprite)

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -21,6 +22,7 @@
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/util/clipboard.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "doc/image.h"
#include "doc/palette.h"
@ -65,7 +67,9 @@ PaletteView::PaletteView(bool editable, PaletteViewStyle style, PaletteViewDeleg
setFocusStop(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(
[this]{

View File

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

View File

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

View File

@ -1,4 +1,7 @@
# Aseprite Document IO Library
*Copyright (C) 2016-2018 David Capello*
> 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
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// 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_CEL 0x2005
#define ASE_FILE_CHUNK_CEL_EXTRA 0x2006
#define ASE_FILE_CHUNK_COLOR_PROFILE 0x2007
#define ASE_FILE_CHUNK_MASK 0x2016
#define ASE_FILE_CHUNK_PATH 0x2017
#define ASE_FILE_CHUNK_FRAME_TAGS 0x2018
@ -33,6 +35,12 @@
#define ASE_FILE_LINK_CEL 1
#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_USER_DATA_FLAG_HAS_TEXT 1

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -14,6 +15,7 @@
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/fs.h"
#include "gfx/color_space.h"
#include "dio/aseprite_common.h"
#include "dio/decode_delegate.h"
#include "dio/file_interface.h"
@ -152,6 +154,11 @@ bool AsepriteDecoder::decode()
break;
}
case ASE_FILE_CHUNK_COLOR_PROFILE: {
readColorProfile(sprite.get());
break;
}
case ASE_FILE_CHUNK_MASK: {
doc::Mask* mask = readMaskChunk();
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()
{
int c, u, v, byte;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// 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)
: Object(ObjectType::Sprite)
, m_document(NULL)
, m_document(nullptr)
, m_spec((ColorMode)format, width, height, 0)
, m_pixelRatio(1, 1)
, m_frames(1)
@ -149,6 +150,11 @@ void Sprite::setSize(int width, int height)
m_spec.setSize(width, height);
}
void Sprite::setColorSpace(const gfx::ColorSpacePtr& colorSpace)
{
m_spec.setColorSpace(colorSpace);
}
bool Sprite::needAlpha() const
{
switch (pixelFormat()) {

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2018 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// 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(); }
int width() const { return m_spec.width(); }
int height() const { return m_spec.height(); }
const gfx::ColorSpacePtr& colorSpace() const { return m_spec.colorSpace(); }
void setPixelFormat(PixelFormat format);
void setPixelRatio(const PixelRatio& pixelRatio);
void setSize(int width, int height);
void setColorSpace(const gfx::ColorSpacePtr& colorSpace);
// Returns true if the rendered images will contain alpha values less
// than 255. Only RGBA and Grayscale images without background needs