mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-28 16:11:35 +00:00
Manage color profiles (fix #1576)
This commit is contained in:
parent
f2739d89f1
commit
a4d8fc52bf
@ -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
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Aseprite
|
||||
*Copyright (C) 2001-2018 David Capello*
|
||||
|
||||
[](https://travis-ci.org/aseprite/aseprite)
|
||||
[](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
|
||||
|
||||
|
@ -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=""sRGB"" />
|
||||
<option id="files_with_profile" type="ColorProfileBehavior" default="ColorProfileBehavior::EMBEDDED" />
|
||||
<option id="missing_profile" type="ColorProfileBehavior" default="ColorProfileBehavior::ASSIGN" />
|
||||
</section>
|
||||
</global>
|
||||
|
||||
<tool>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
2
laf
@ -1 +1 @@
|
||||
Subproject commit ba09f989212548d8fc5659cb716398f9407c351a
|
||||
Subproject commit f4a08a23e8de9a482c8167ae084f4148803673be
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
49
src/app/cmd/assign_color_profile.cpp
Normal file
49
src/app/cmd/assign_color_profile.cpp
Normal 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
|
42
src/app/cmd/assign_color_profile.h
Normal file
42
src/app/cmd/assign_color_profile.h
Normal 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
|
104
src/app/cmd/convert_color_profile.cpp
Normal file
104
src/app/cmd/convert_color_profile.cpp
Normal 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
|
43
src/app/cmd/convert_color_profile.h
Normal file
43
src/app/cmd/convert_color_profile.h
Normal 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
79
src/app/color_spaces.cpp
Normal 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
40
src/app/color_spaces.h
Normal 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
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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) { }
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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]{
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user