mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-01 19:20:17 +00:00
Add options to load/save/convert/assign color profiles correctly (#1576)
This commit is contained in:
parent
a4d8fc52bf
commit
ccae016878
@ -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
|
||||
|
||||
|
15
data/widgets/ask_for_color_profile.xml
Normal file
15
data/widgets/ask_for_color_profile.xml
Normal 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>
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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?
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user