Add options to load/save/convert/assign color profiles correctly (#1576)

This commit is contained in:
David Capello 2018-10-23 15:03:38 -03:00
parent a4d8fc52bf
commit ccae016878
11 changed files with 244 additions and 7 deletions

View File

@ -6,6 +6,11 @@
title = Warning - Important
description = You are going to enter in "Advanced Mode".
[ask_for_color_profile]
title = Color Profile
sprite_with_profile = The sprite contains a color profile.
sprite_without_profile = The sprite doesn't contain a color profile.
[statusbar_tips]
all_layers_are_locked = All selected layers are locked

View File

@ -0,0 +1,15 @@
<!-- Aseprite -->
<!-- Copyright (C) 2018 Igara Studio S.A. -->
<gui>
<window id="ask_for_color_profile" text="@.title">
<vbox>
<label text="@.sprite_with_profile" id="sprite_with_profile" />
<label text="@.sprite_without_profile" id="sprite_without_profile" />
<separator horizontal="true" />
<button text="@options.use_embedded_cs" closewindow="true" id="embedded" magnet="true" />
<button text="@options.convert_cs" closewindow="true" id="convert" />
<button text="@options.assign_cs" closewindow="true" id="assign" magnet="true" />
<button text="@options.disable_cs" closewindow="true" id="disable" />
</vbox>
</window>
</gui>

View File

@ -23,6 +23,70 @@
namespace app {
namespace cmd {
void convert_color_profile(doc::Sprite* sprite, const gfx::ColorSpacePtr& newCS)
{
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()));
}
sprite->replaceImage(old_image->id(), 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)
sprite->setPalette(&newPal, false);
}
}
}
sprite->setColorSpace(newCS);
// Generate notification so the Doc::osColorSpace() is regenerated
static_cast<Doc*>(sprite->document())->notifyColorSpaceChanged();
}
ConvertColorProfile::ConvertColorProfile(doc::Sprite* sprite, const gfx::ColorSpacePtr& newCS)
: WithSprite(sprite)
{

View File

@ -37,6 +37,10 @@ namespace cmd {
CmdSequence m_seq;
};
// Converts the sprite to the new color profile without undo information.
// TODO how to merge this function with cmd::ConvertColorProfile
void convert_color_profile(doc::Sprite* sprite, const gfx::ColorSpacePtr& newCS);
} // namespace cmd
} // namespace app

View File

@ -12,6 +12,7 @@
#include "app/doc.h"
#include "app/modules/editors.h"
#include "app/pref/preferences.h"
#include "app/ui/editor/editor.h"
#include "os/display.h"
#include "os/system.h"
@ -31,6 +32,23 @@ os::ColorSpacePtr get_current_color_space()
return get_screen_color_space();
}
gfx::ColorSpacePtr get_working_rgb_space_from_preferences()
{
if (Preferences::instance().color.manage()) {
const std::string name = Preferences::instance().color.workingRgbSpace();
if (name == "sRGB")
return gfx::ColorSpace::MakeSRGB();
std::vector<os::ColorSpacePtr> colorSpaces;
os::instance()->listColorSpaces(colorSpaces);
for (auto& cs : colorSpaces) {
if (cs->gfxColorSpace()->name() == name)
return cs->gfxColorSpace();
}
}
return gfx::ColorSpace::MakeNone();
}
//////////////////////////////////////////////////////////////////////
// Color conversion

View File

@ -9,10 +9,15 @@
#pragma once
#include "gfx/color.h"
#include "gfx/color_space.h"
#include "os/color_space.h"
#include <memory>
namespace doc {
class Sprite;
}
namespace app {
os::ColorSpacePtr get_screen_color_space();
@ -20,6 +25,8 @@ namespace app {
// Returns the color space of the current document.
os::ColorSpacePtr get_current_color_space();
gfx::ColorSpacePtr get_working_rgb_space_from_preferences();
class ConvertCS {
public:
ConvertCS();

View File

@ -200,7 +200,14 @@ bool AseFormat::onLoad(FileOp* fop)
if (!decoder.decode())
return false;
fop->createDocument(delegate.sprite());
Sprite* sprite = delegate.sprite();
fop->createDocument(sprite);
if (sprite->colorSpace() != nullptr &&
sprite->colorSpace()->type() != gfx::ColorSpace::None) {
fop->setEmbeddedColorProfile();
}
return true;
}
@ -270,7 +277,7 @@ bool AseFormat::onSave(FileOp* fop)
frame_header.duration = sprite->frameDuration(frame);
// Save color profile in first frame
if (outputFrame == 0)
if (outputFrame == 0 && fop->preserveColorProfile())
ase_file_write_color_profile(f, &frame_header, sprite);
// is the first frame or did the palette change?

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
@ -10,6 +11,8 @@
#include "app/file/file.h"
#include "app/cmd/convert_color_profile.h"
#include "app/color_spaces.h"
#include "app/console.h"
#include "app/context.h"
#include "app/doc.h"
@ -23,6 +26,7 @@
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/tx.h"
#include "app/ui/optional_alert.h"
#include "app/ui/status_bar.h"
#include "base/fs.h"
@ -35,8 +39,10 @@
#include "fmt/format.h"
#include "render/quantization.h"
#include "render/render.h"
#include "ui/alert.h"
#include "ui/listitem.h"
#include "ask_for_color_profile.xml.h"
#include "open_sequence.xml.h"
#include <cstring>
@ -874,6 +880,91 @@ void FileOp::postLoad()
}
}
// What to do with the sprite color profile?
gfx::ColorSpacePtr spriteCS = sprite->colorSpace();
app::gen::ColorProfileBehavior behavior =
app::gen::ColorProfileBehavior::DISABLE;
if (Preferences::instance().color.manage()) {
// Embedded color profile
if (this->hasEmbeddedColorProfile()) {
behavior = Preferences::instance().color.filesWithProfile();
if (behavior == app::gen::ColorProfileBehavior::ASK) {
#ifdef ENABLE_UI
if (m_context && m_context->isUIAvailable()) {
app::gen::AskForColorProfile window;
window.spriteWithoutProfile()->setVisible(false);
window.openWindowInForeground();
auto c = window.closer();
if (c == window.embedded())
behavior = app::gen::ColorProfileBehavior::EMBEDDED;
else if (c == window.convert())
behavior = app::gen::ColorProfileBehavior::CONVERT;
else if (c == window.assign())
behavior = app::gen::ColorProfileBehavior::ASSIGN;
else
behavior = app::gen::ColorProfileBehavior::DISABLE;
}
else
#endif // ENABLE_UI
{
behavior = app::gen::ColorProfileBehavior::EMBEDDED;
}
}
}
// Missing color space
else {
behavior = Preferences::instance().color.missingProfile();
if (behavior == app::gen::ColorProfileBehavior::ASK) {
#ifdef ENABLE_UI
if (m_context && m_context->isUIAvailable()) {
app::gen::AskForColorProfile window;
window.spriteWithProfile()->setVisible(false);
window.embedded()->setVisible(false);
window.convert()->setVisible(false);
window.openWindowInForeground();
if (window.closer() == window.assign()) {
behavior = app::gen::ColorProfileBehavior::ASSIGN;
}
else {
behavior = app::gen::ColorProfileBehavior::DISABLE;
}
}
else
#endif // ENABLE_UI
{
behavior = app::gen::ColorProfileBehavior::ASSIGN;
}
}
}
}
switch (behavior) {
case app::gen::ColorProfileBehavior::DISABLE:
sprite->setColorSpace(gfx::ColorSpace::MakeNone());
break;
case app::gen::ColorProfileBehavior::EMBEDDED:
// Do nothing, just keep the current sprite's color sprite
break;
case app::gen::ColorProfileBehavior::CONVERT: {
// Convert to the working color profile
auto gfxCS = get_working_rgb_space_from_preferences();
if (!gfxCS->nearlyEqual(*spriteCS))
cmd::convert_color_profile(sprite, gfxCS);
break;
}
case app::gen::ColorProfileBehavior::ASSIGN: {
// Convert to the working color profile
auto gfxCS = get_working_rgb_space_from_preferences();
sprite->setColorSpace(gfxCS);
break;
}
}
m_document->markAsSaved();
}
@ -1062,6 +1153,9 @@ FileOp::FileOp(FileOpType type, Context* context)
, m_done(false)
, m_stop(false)
, m_oneframe(false)
, m_preserveColorProfile(
Preferences::instance().color.manage())
, m_embeddedColorProfile(false)
{
m_seq.palette = nullptr;
m_seq.image.reset(nullptr);

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
@ -110,6 +111,7 @@ namespace app {
bool isSequence() const { return !m_seq.filename_list.empty(); }
bool isOneFrame() const { return m_oneframe; }
bool preserveColorProfile() const { return m_preserveColorProfile; }
const std::string& filename() const { return m_filename; }
const base::paths& filenames() const { return m_seq.filename_list; }
@ -167,6 +169,9 @@ namespace app {
void getFilenameList(base::paths& output) const;
void setEmbeddedColorProfile() { m_embeddedColorProfile = true; }
bool hasEmbeddedColorProfile() const { return m_embeddedColorProfile; }
private:
FileOp(); // Undefined
FileOp(FileOpType type, Context* context);
@ -192,6 +197,13 @@ namespace app {
// that support animation like
// GIF/FLI/ASE).
// Return if we've to save/embed the color space of the document
// in the file.
bool m_preserveColorProfile;
// True if the file contained a color profile when it was loaded.
bool m_embeddedColorProfile;
base::SharedPtr<FormatOptions> m_formatOptions;
// Data for sequences.

View File

@ -256,6 +256,11 @@ bool JpegFormat::onLoad(FileOp* fop)
// Read color space
gfx::ColorSpacePtr colorSpace = loadColorSpace(fop, &dinfo);
if (colorSpace)
fop->setEmbeddedColorProfile();
else { // sRGB is the default JPG color space.
colorSpace = gfx::ColorSpace::MakeSRGB();
}
if (colorSpace &&
fop->document()->sprite()->colorSpace()->type() == gfx::ColorSpace::None) {
fop->document()->sprite()->setColorSpace(colorSpace);
@ -390,7 +395,8 @@ bool JpegFormat::onSave(FileOp* fop)
jpeg_start_compress(&cinfo, true);
// Save color space
if (fop->document()->sprite()->colorSpace())
if (fop->preserveColorProfile() &&
fop->document()->sprite()->colorSpace())
saveColorSpace(fop, &cinfo, fop->document()->sprite()->colorSpace().get());
// CREATE the buffer.

View File

@ -372,6 +372,11 @@ bool PngFormat::onLoad(FileOp* fop)
// Setup the color space.
auto colorSpace = PngFormat::loadColorSpace(png_ptr, info_ptr);
if (colorSpace)
fop->setEmbeddedColorProfile();
else { // sRGB is the default PNG color space.
colorSpace = gfx::ColorSpace::MakeSRGB();
}
if (colorSpace &&
fop->document()->sprite()->colorSpace()->type() == gfx::ColorSpace::None) {
fop->document()->sprite()->setColorSpace(colorSpace);
@ -449,9 +454,8 @@ gfx::ColorSpacePtr PngFormat::loadColorSpace(png_structp png_ptr, png_infop info
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();
// No color space.
return nullptr;
}
#ifdef ENABLE_SAVE
@ -516,7 +520,8 @@ 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())
if (fop->preserveColorProfile() &&
fop->document()->sprite()->colorSpace())
saveColorSpace(png_ptr, info_ptr, fop->document()->sprite()->colorSpace().get());
if (color_type == PNG_COLOR_TYPE_PALETTE) {