mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Merge branch 'beta-psd' into beta
This commit is contained in:
commit
72cf9c12f7
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
478
src/app/file/psd_format.cpp
Normal 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
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
1
src/psd
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5a357fb964ef158bce4b1774157dc16a8bcd1f8d
|
Loading…
x
Reference in New Issue
Block a user