Merge branch 'beta-psd' into beta

This commit is contained in:
David Capello 2021-12-15 17:27:33 -03:00
commit 72cf9c12f7
8 changed files with 498 additions and 0 deletions

3
.gitmodules vendored
View File

@ -75,3 +75,6 @@
[submodule "third_party/cityhash"]
path = third_party/cityhash
url = https://github.com/aseprite/cityhash.git
[submodule "src/psd"]
path = src/psd
url = https://github.com/aseprite/psd.git

View File

@ -102,6 +102,7 @@ add_subdirectory(doc)
add_subdirectory(filters)
add_subdirectory(fixmath)
add_subdirectory(flic)
add_subdirectory(psd)
add_subdirectory(tga)
add_subdirectory(render)
add_subdirectory(dio)

View File

@ -132,6 +132,7 @@ set(file_formats
file/jpeg_format.cpp
file/pcx_format.cpp
file/png_format.cpp
file/psd_format.cpp
file/svg_format.cpp
file/tga_format.cpp)
if(WITH_WEBP_SUPPORT)
@ -676,6 +677,7 @@ target_link_libraries(app-lib
render-lib
laf-ft
laf-os
psd
ui-lib
ver-lib
undo

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -29,6 +30,7 @@ extern FileFormat* CreateIcoFormat();
extern FileFormat* CreateJpegFormat();
extern FileFormat* CreatePcxFormat();
extern FileFormat* CreatePngFormat();
extern FileFormat* CreatePsdFormat();
extern FileFormat* CreateSvgFormat();
extern FileFormat* CreateTgaFormat();
@ -65,6 +67,7 @@ FileFormatsManager::FileFormatsManager()
registerFormat(CreateJpegFormat());
registerFormat(CreatePcxFormat());
registerFormat(CreatePngFormat());
registerFormat(CreatePsdFormat());
registerFormat(CreateSvgFormat());
registerFormat(CreateTgaFormat());

478
src/app/file/psd_format.cpp Normal file
View File

@ -0,0 +1,478 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "base/file_handle.h"
#include "doc/blend_mode.h"
#include "doc/image_impl.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "psd/psd.h"
namespace app {
doc::PixelFormat psd_cmode_to_ase_format(const psd::ColorMode mode)
{
switch (mode) {
case psd::ColorMode::Grayscale:
return doc::PixelFormat::IMAGE_GRAYSCALE;
case psd::ColorMode::RGB:
return doc::PixelFormat::IMAGE_RGB;
default:
return doc::PixelFormat::IMAGE_INDEXED;
}
}
doc::BlendMode psd_blendmode_to_ase(const psd::LayerBlendMode mode)
{
switch (mode) {
case psd::LayerBlendMode::Multiply:
return doc::BlendMode::MULTIPLY;
case psd::LayerBlendMode::Darken:
return doc::BlendMode::DARKEN;
case psd::LayerBlendMode::ColorBurn:
return doc::BlendMode::COLOR_BURN;
case psd::LayerBlendMode::Lighten:
return doc::BlendMode::LIGHTEN;
case psd::LayerBlendMode::Screen:
return doc::BlendMode::SCREEN;
case psd::LayerBlendMode::ColorDodge:
return doc::BlendMode::COLOR_DODGE;
case psd::LayerBlendMode::Overlay:
return doc::BlendMode::OVERLAY;
case psd::LayerBlendMode::SoftLight:
return doc::BlendMode::SOFT_LIGHT;
case psd::LayerBlendMode::HardLight:
return doc::BlendMode::HARD_LIGHT;
case psd::LayerBlendMode::Difference:
return doc::BlendMode::DIFFERENCE;
case psd::LayerBlendMode::Exclusion:
return doc::BlendMode::EXCLUSION;
case psd::LayerBlendMode::Subtract:
return doc::BlendMode::SUBTRACT;
case psd::LayerBlendMode::Divide:
return doc::BlendMode::DIVIDE;
case psd::LayerBlendMode::Hue:
return doc::BlendMode::HSL_HUE;
case psd::LayerBlendMode::Saturation:
return doc::BlendMode::HSL_SATURATION;
case psd::LayerBlendMode::Color:
return doc::BlendMode::HSL_COLOR;
case psd::LayerBlendMode::Luminosity:
return doc::BlendMode::HSL_LUMINOSITY;
case psd::LayerBlendMode::Normal:
default:
return doc::BlendMode::NORMAL;
}
}
class PsdFormat : public FileFormat {
const char* onGetName() const override { return "psd"; }
void onGetExtensions(base::paths& exts) const override
{
exts.push_back("psb");
exts.push_back("psd");
}
dio::FileFormat onGetDioFormat() const override
{
return dio::FileFormat::PSD_IMAGE;
}
int onGetFlags() const override { return FILE_SUPPORT_LOAD; }
bool onLoad(FileOp* fop) override;
bool onSave(FileOp* fop) override;
};
FileFormat* CreatePsdFormat()
{
return new PsdFormat();
}
class PsdDecoderDelegate : public psd::DecoderDelegate {
public:
PsdDecoderDelegate()
: m_currentImage(nullptr)
, m_currentLayer(nullptr)
, m_layerGroup(nullptr)
, m_sprite(nullptr)
, m_activeFrameIndex(0)
, m_pixelFormat(PixelFormat::IMAGE_INDEXED)
, m_layerHasTransparentChannel(false)
{ }
Sprite* getSprite() { return assembleDocument(); }
void onFileHeader(const psd::FileHeader& header) override
{
m_pixelFormat = psd_cmode_to_ase_format(header.colorMode);
m_sprite = new Sprite(
ImageSpec(ColorMode(m_pixelFormat), header.width, header.width));
m_layerHasTransparentChannel = hasTransparency(header.nchannels);
}
void onSlicesData(const psd::Slices& slices) override
{
auto& spriteSlices = m_sprite->slices();
for (const auto& slice : slices.slices) {
const gfx::Rect rect(slice.bound.left,
slice.bound.top,
slice.bound.right - slice.bound.left,
slice.bound.bottom - slice.bound.top);
if (!rect.isEmpty() && slice.groupID > 0) {
auto slice = new doc::Slice;
slice->insert(0, doc::SliceKey(rect));
// TO-DO: the slices color should come from the app preference
slice->userData().setColor(doc::rgba(0, 0, 255, 255));
spriteSlices.add(slice);
}
}
}
void onFramesData(const std::vector<psd::FrameInformation>& frameInfo,
const uint32_t activeFrameIndex) override
{
m_framesInfo = frameInfo;
m_activeFrameIndex = activeFrameIndex;
if (frameInfo.empty()) {
m_sprite->setTotalFrames(frame_t(1));
psd::FrameInformation frameInfo;
frameInfo.id = 1;
frameInfo.duration = 10;
frameInfo.ga = 0;
m_framesInfo.push_back(std::move(frameInfo));
}
else
m_sprite->setTotalFrames(frame_t(frameInfo.size()));
}
// Emitted when a new layer has been chosen and its channel image data
// is about to be read
void onBeginLayer(const psd::LayerRecord& layerRecord) override
{
if (layerRecord.isOpenGroup()) {
LayerGroup* layerGroup = new LayerGroup(m_sprite);
if (m_groups.empty())
m_sprite->root()->addLayer(layerGroup);
else
m_layerGroup->addLayer(layerGroup);
m_layerGroup = layerGroup;
m_groups.push_back(layerGroup);
}
else if (layerRecord.isCloseGroup()) {
if (!m_layerGroup)
throw std::runtime_error("unexpected end of a group layer");
m_layerGroup->setName(layerRecord.name);
if (!m_groups.empty())
m_groups.pop_back();
if (m_groups.empty())
m_layerGroup = m_sprite->root();
else
m_layerGroup = m_groups.back();
}
else {
auto findIter = std::find_if(
m_layers.begin(), m_layers.end(), [&layerRecord](doc::Layer* layer) {
return layer->name() == layerRecord.name;
});
if (findIter == m_layers.end()) {
if (!m_layerGroup) // In this case, there are no layer groups
m_layerGroup = m_sprite->root();
createNewLayer(layerRecord.name);
//m_currentLayer->setVisible(layerRecord.isVisible());
m_layerHasTransparentChannel =
hasTransparency(layerRecord.channels.size());
}
else {
m_currentLayer = *findIter;
m_currentImage = m_currentLayer->cel(frame_t(0))->imageRef();
}
}
}
void onEndLayer(const psd::LayerRecord& layerRecord) override
{
if (!m_framesInfo.empty() &&
(layerRecord.inFrames.size() == m_framesInfo.size()) &&
m_currentImage) {
std::unique_ptr<Cel> layerCel(m_currentLayer->cel(frame_t(0)));
LayerImage* imageLayer = static_cast<LayerImage*>(m_currentLayer);
imageLayer->removeCel(layerCel.get());
for (const auto& inFrame : layerRecord.inFrames) {
if (inFrame.isVisibleInFrame) {
const auto findIter = std::find_if(
m_framesInfo.cbegin(),
m_framesInfo.cend(),
[id = inFrame.frameID](const psd::FrameInformation& frameInfo) {
return id == frameInfo.id;
});
if (findIter != m_framesInfo.cend()) {
const size_t index = std::distance(m_framesInfo.cbegin(), findIter);
auto newCel = Cel::MakeCopy(index, layerCel.get());
imageLayer->addCel(newCel);
}
}
}
}
m_currentImage.reset();
m_currentLayer = nullptr;
m_layerHasTransparentChannel = false;
}
// Emitted only if there's a palette in an image
void onColorModeData(const psd::ColorModeData& colorModeD) override
{
if (!colorModeD.colors.empty()) {
for (int i = 0; i < colorModeD.colors.size(); ++i) {
const psd::IndexColor iColor = colorModeD.colors[i];
m_palette.setEntry(i, rgba(iColor.r, iColor.g, iColor.b, 255));
}
}
}
// Emitted when an image data is about to be transmitted
void onBeginImage(const psd::ImageData& imageData) override
{
if (!m_currentImage) {
// Only occurs where there's an image with no layer
if (m_layers.empty()) {
m_layerGroup = m_sprite->root();
createNewLayer("Layer 1");
m_layerHasTransparentChannel =
hasTransparency(imageData.channels.size());
}
if (m_currentLayer) {
createNewImage(imageData.width, imageData.height);
linkNewCel(m_currentLayer, m_currentImage);
}
}
}
// Emitted when all layers and their masks have been processed
void onLayersAndMask(const psd::LayersInformation& layersInfo) override
{
if (layersInfo.layers.size() == m_layers.size()) {
for (int i = 0; i < m_layers.size(); ++i) {
const psd::LayerRecord& layerRecord = layersInfo.layers[i];
LayerImage* layer = static_cast<LayerImage*>(m_layers[i]);
layer->setBlendMode(psd_blendmode_to_ase(layerRecord.blendMode));
for (size_t i = 0; i < m_sprite->totalFrames(); ++i) {
Cel* cel = layer->cel(frame_t(i));
if (cel) {
cel->setOpacity(layerRecord.opacity);
cel->setPosition(gfx::Point(layerRecord.left, layerRecord.top));
}
}
}
}
}
void onImageScanline(const psd::ImageData& img,
const int y,
const psd::ChannelID chanID,
const uint8_t* data,
const int bytes) override
{
if (!m_currentImage || y >= m_currentImage->height())
return;
const int dataCount = bytes / (img.depth >= 8 ? (img.depth / 8) : 1);
uint8_t* dstGenericAddress = m_currentImage->getPixelAddress(0, y);
if (m_pixelFormat == doc::PixelFormat::IMAGE_INDEXED) {
IndexedTraits::address_t dstAddress =
(IndexedTraits::address_t)dstGenericAddress;
for (int x = 0; x < dataCount && x < m_currentImage->width(); ++x) {
*(dstAddress)++ = getNormalizedPixelValue(data, img.depth);
}
}
else if (m_pixelFormat == doc::PixelFormat::IMAGE_GRAYSCALE) {
GrayscaleTraits::address_t dstAddress =
(GrayscaleTraits::address_t)dstGenericAddress;
uint8_t v = 0, a = 0;
for (int x = 0; x < dataCount && x < m_currentImage->width(); ++x) {
const GrayscaleTraits::pixel_t pixel = *dstAddress;
const uint8_t newPixelValue = getNormalizedPixelValue(data, img.depth);
if (chanID == psd::ChannelID::Red) {
v = newPixelValue;
a = m_layerHasTransparentChannel ? graya_geta(pixel) : 255;
}
else if (chanID == psd::ChannelID::Alpha ||
chanID == psd::ChannelID::TransparencyMask) {
a = newPixelValue;
v = graya_getv(pixel);
}
*(dstAddress++) = graya(v, a);
}
}
else if (m_pixelFormat == doc::PixelFormat::IMAGE_RGB) {
RgbTraits::address_t dstAddress = (RgbTraits::address_t)dstGenericAddress;
uint8_t r, g, b, a;
for (int x = 0; x < dataCount && x < m_currentImage->width(); ++x) {
const uint8_t newPixelValue = getNormalizedPixelValue(data, img.depth);
const color_t c = *(dstAddress);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = m_layerHasTransparentChannel ? rgba_geta(c) : 255;
if (chanID == psd::ChannelID::Red) {
r = newPixelValue;
}
else if (chanID == psd::ChannelID::Green) {
g = newPixelValue;
}
else if (chanID == psd::ChannelID::Blue) {
b = newPixelValue;
}
else if (chanID == psd::ChannelID::Alpha ||
chanID == psd::ChannelID::TransparencyMask) {
a = newPixelValue;
}
*(dstAddress++) = rgba(r, g, b, a);
}
}
}
private:
inline bool hasTransparency(const size_t nchannels)
{
// RGBA or grayscale image with alpha channel
return nchannels == 4 || nchannels == 2;
}
void linkNewCel(Layer* layer, doc::ImageRef image)
{
if (!image)
return;
std::unique_ptr<Cel> cel(new doc::Cel(frame_t(0), image));
cel->setPosition(0, 0);
static_cast<LayerImage*>(layer)->addCel(cel.release());
}
void createNewLayer(const std::string& layerName)
{
m_currentLayer = new LayerImage(m_sprite);
m_layerGroup->addLayer(m_currentLayer);
m_layers.push_back(m_currentLayer);
m_currentLayer->setName(layerName);
}
Sprite* assembleDocument()
{
if (m_palette.getModifications() > 1)
m_sprite->setPalette(&m_palette, true);
for (size_t i = 0; i < m_framesInfo.size(); ++i) {
const psd::FrameInformation& frameInfo = m_framesInfo[i];
const int timeMs = frameInfo.duration * 10;
m_sprite->setFrameDuration(frame_t(i), timeMs);
}
// at this point, we're supposed to be able to set the activeFrame
return m_sprite;
}
std::uint8_t getNormalizedPixelValue(const std::uint8_t*& data,
const int depth)
{
if (depth == 1 || depth == 8) {
return *(data++);
}
else if (depth == 16) {
const uint16_t value = int(data[0]) | int(data[1] << 8);
data += 2;
return value >> 8;
}
else if (depth == 32) {
const uint32_t value = int(data[0] << 24) | int(data[1] << 16) |
int(data[2] << 8) | int(data[3]);
data += 4;
return value >> 24;
}
else
throw std::runtime_error("invalid image depth");
}
void createNewImage(const int width, const int height)
{
if (width <= 0 || height <= 0)
return; //throw std::runtime_error("invalid image width/height");
m_currentImage.reset(Image::create(m_pixelFormat, width, height));
clear_image(m_currentImage.get(), 0);
}
doc::ImageRef m_currentImage;
doc::Layer* m_currentLayer;
doc::LayerGroup* m_layerGroup;
Sprite* m_sprite;
uint32_t m_activeFrameIndex;
PixelFormat m_pixelFormat;
std::vector<doc::Layer*> m_layers;
std::vector<doc::LayerGroup*> m_groups;
std::vector<psd::FrameInformation> m_framesInfo;
Palette m_palette;
bool m_layerHasTransparentChannel;
};
bool PsdFormat::onLoad(FileOp* fop)
{
base::FileHandle fileHandle =
base::open_file_with_exception(fop->filename(), "rb");
FILE* f = fileHandle.get();
psd::StdioFileInterface fileInterface(f);
PsdDecoderDelegate pDelegate;
psd::Decoder decoder(&fileInterface, &pDelegate);
if (!decoder.readFileHeader()) {
fop->setError("The file doesn't have a valid PSD header\n");
return false;
}
const psd::FileHeader header = decoder.fileHeader();
if (header.colorMode != psd::ColorMode::RGB &&
header.colorMode != psd::ColorMode::Indexed &&
header.colorMode != psd::ColorMode::Grayscale) {
fop->setError("This preliminary work only supports "
"RGB, Grayscale & Indexed images\n");
return false;
}
try {
decoder.readColorModeData();
decoder.readImageResources();
decoder.readLayersAndMask();
decoder.readImageData();
}
catch (const std::runtime_error& e) {
fop->setError(e.what());
return false;
}
fop->createDocument(pDelegate.getSprite());
return true;
}
bool PsdFormat::onSave(FileOp* fop)
{
return false;
}
} // namespace app

View File

@ -23,6 +23,7 @@
#define PNG_MAGIC_DWORD2 0x0A1A0A0D
#define WEBP_STAMP_1 "RIFF" // "RIFFnnnnWEBP"
#define WEBP_STAMP_2 "WEBP"
#define PSD_STAMP "8BPS"
namespace dio {
@ -65,6 +66,9 @@ FileFormat detect_format_by_file_content_bytes(const uint8_t* buf,
std::strncmp((const char*)buf, GIF_89_STAMP, 6) == 0)
return FileFormat::GIF_ANIMATION;
if (std::strncmp((const char*)buf, PSD_STAMP, 4) == 0)
return FileFormat::PSD_IMAGE;
if (IS_MAGIC_WORD(4, ASE_MAGIC_NUMBER))
return FileFormat::ASE_ANIMATION;
@ -156,6 +160,10 @@ FileFormat detect_format_by_file_extension(const std::string& filename)
if (ext == "webp")
return FileFormat::WEBP_ANIMATION;
if (ext == "psd" ||
ext == "psb")
return FileFormat::PSD_IMAGE;
return FileFormat::UNKNOWN;
}

View File

@ -1,4 +1,5 @@
// Aseprite Document IO Library
// Copyright (c) 2021 Igara Studio S.A.
// Copyright (c) 2016-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -32,6 +33,7 @@ enum class FileFormat {
TARGA_IMAGE,
WEBP_ANIMATION,
CSS_STYLE,
PSD_IMAGE,
};
} // namespace dio

1
src/psd Submodule

@ -0,0 +1 @@
Subproject commit 5a357fb964ef158bce4b1774157dc16a8bcd1f8d