mirror of https://github.com/aseprite/aseprite.git
Save/load blend mode/opacity for group layers when needed (#3225)
This commit is contained in:
parent
d46d791339
commit
76a8a8af7a
|
|
@ -75,8 +75,10 @@ A 128-byte header (same as FLC/FLI header, but with other magic number):
|
|||
32 bpp = RGBA
|
||||
16 bpp = Grayscale
|
||||
8 bpp = Indexed
|
||||
DWORD Flags:
|
||||
DWORD Flags (see NOTE.6):
|
||||
1 = Layer opacity has valid value
|
||||
2 = Layer blend mode/opacity is valid for groups
|
||||
(composite groups separately first when rendering)
|
||||
WORD Speed (milliseconds between frame, like in FLC files)
|
||||
DEPRECATED: You should use the frame duration field
|
||||
from each frame header
|
||||
|
|
@ -175,7 +177,7 @@ entire layers layout:
|
|||
WORD Layer child level (see NOTE.1)
|
||||
WORD Default layer width in pixels (ignored)
|
||||
WORD Default layer height in pixels (ignored)
|
||||
WORD Blend mode (always 0 for layer set)
|
||||
WORD Blend mode (see NOTE.6)
|
||||
Normal = 0
|
||||
Multiply = 1
|
||||
Screen = 2
|
||||
|
|
@ -195,8 +197,7 @@ entire layers layout:
|
|||
Addition = 16
|
||||
Subtract = 17
|
||||
Divide = 18
|
||||
BYTE Opacity
|
||||
Note: valid only if file header flags field has bit 1 set
|
||||
BYTE Opacity (see NOTE.6)
|
||||
BYTE[3] For future (set to zero)
|
||||
STRING Layer name
|
||||
+ If layer type = 2
|
||||
|
|
@ -609,6 +610,14 @@ if this value is the same, we compare the specific `zIndex` value to
|
|||
disambiguate some scenarios. An example of this implementation can be
|
||||
found in the [RenderPlan code](https://github.com/aseprite/aseprite/blob/8e91d22b704d6d1e95e1482544318cee9f166c4d/src/doc/render_plan.cpp#L77).
|
||||
|
||||
### NOTE.6
|
||||
|
||||
The blend mode and opacity fields of Layer Chunks (0x2004) are always
|
||||
valid for image and tilemap layers. The opacity field only when the
|
||||
[main header](#header) "Flags" field has the bit 1 enabled. Both
|
||||
fields are valid for group layers too when the same "Flags" field has
|
||||
the bit 2.
|
||||
|
||||
## File Format Changes
|
||||
|
||||
1. The first change from the first release of the new .ase format,
|
||||
|
|
|
|||
|
|
@ -145,7 +145,8 @@ public:
|
|||
} // anonymous namespace
|
||||
|
||||
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
|
||||
const frame_t firstFrame, const frame_t totalFrames);
|
||||
frame_t firstFrame, frame_t totalFrames,
|
||||
bool composeGroups);
|
||||
static void ase_file_write_header(FILE* f, dio::AsepriteHeader* header);
|
||||
static void ase_file_write_header_filesize(FILE* f, dio::AsepriteHeader* header);
|
||||
|
||||
|
|
@ -153,6 +154,7 @@ static void ase_file_prepare_frame_header(FILE* f, dio::AsepriteFrameHeader* fra
|
|||
static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame_header);
|
||||
|
||||
static void ase_file_write_layers(FILE* f, FileOp* fop,
|
||||
const dio::AsepriteHeader* header,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Layer* layer, int child_level);
|
||||
|
|
@ -171,7 +173,10 @@ static void ase_file_write_close_chunk(FILE* f, dio::AsepriteChunk* chunk);
|
|||
|
||||
static void ase_file_write_color2_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal);
|
||||
static void ase_file_write_palette_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Palette* pal, int from, int to);
|
||||
static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level);
|
||||
static void ase_file_write_layer_chunk(FILE* f, const dio::AsepriteHeader* header,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Layer* layer,
|
||||
int child_level);
|
||||
static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header,
|
||||
const Cel* cel,
|
||||
const LayerImage* layer,
|
||||
|
|
@ -350,7 +355,8 @@ bool AseFormat::onSave(FileOp* fop)
|
|||
dio::AsepriteHeader header;
|
||||
ase_file_prepare_header(f, &header, sprite,
|
||||
fop->roi().fromFrame(),
|
||||
fop->roi().frames());
|
||||
fop->roi().frames(),
|
||||
fop->config().composeGroups);
|
||||
ase_file_write_header(f, &header);
|
||||
|
||||
bool require_new_palette_chunk = false;
|
||||
|
|
@ -427,7 +433,7 @@ bool AseFormat::onSave(FileOp* fop)
|
|||
// before layers so older version don't get confused by the new
|
||||
// user data chunks for tags.
|
||||
for (Layer* child : sprite->root()->layers())
|
||||
ase_file_write_layers(f, fop, &frame_header, ext_files, child, 0);
|
||||
ase_file_write_layers(f, fop, &header, &frame_header, ext_files, child, 0);
|
||||
|
||||
// Write slice chunks
|
||||
ase_file_write_slice_chunks(f, fop, &frame_header,
|
||||
|
|
@ -468,8 +474,11 @@ bool AseFormat::onSave(FileOp* fop)
|
|||
|
||||
#endif // ENABLE_SAVE
|
||||
|
||||
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const Sprite* sprite,
|
||||
const frame_t firstFrame, const frame_t totalFrames)
|
||||
static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header,
|
||||
const Sprite* sprite,
|
||||
const frame_t firstFrame,
|
||||
const frame_t totalFrames,
|
||||
const bool composeGroups)
|
||||
{
|
||||
header->pos = ftell(f);
|
||||
|
||||
|
|
@ -481,7 +490,8 @@ static void ase_file_prepare_header(FILE* f, dio::AsepriteHeader* header, const
|
|||
header->depth = (sprite->pixelFormat() == IMAGE_RGB ? 32:
|
||||
sprite->pixelFormat() == IMAGE_GRAYSCALE ? 16:
|
||||
sprite->pixelFormat() == IMAGE_INDEXED ? 8: 0);
|
||||
header->flags = ASE_FILE_FLAG_LAYER_WITH_OPACITY;
|
||||
header->flags = (ASE_FILE_FLAG_LAYER_WITH_OPACITY |
|
||||
(composeGroups ? ASE_FILE_FLAG_COMPOSITE_GROUPS: 0));
|
||||
header->speed = sprite->frameDuration(firstFrame);
|
||||
header->next = 0;
|
||||
header->frit = 0;
|
||||
|
|
@ -569,17 +579,18 @@ static void ase_file_write_frame_header(FILE* f, dio::AsepriteFrameHeader* frame
|
|||
}
|
||||
|
||||
static void ase_file_write_layers(FILE* f, FileOp* fop,
|
||||
const dio::AsepriteHeader* header,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const dio::AsepriteExternalFiles& ext_files,
|
||||
const Layer* layer, int child_index)
|
||||
{
|
||||
ase_file_write_layer_chunk(f, frame_header, layer, child_index);
|
||||
ase_file_write_layer_chunk(f, header, frame_header, layer, child_index);
|
||||
if (!layer->userData().isEmpty())
|
||||
ase_file_write_user_data_chunk(f, fop, frame_header, ext_files, &layer->userData());
|
||||
|
||||
if (layer->isGroup()) {
|
||||
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers())
|
||||
ase_file_write_layers(f, fop, frame_header, ext_files, child, child_index+1);
|
||||
ase_file_write_layers(f, fop, header, frame_header, ext_files, child, child_index+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -709,7 +720,11 @@ static void ase_file_write_palette_chunk(FILE* f, dio::AsepriteFrameHeader* fram
|
|||
}
|
||||
}
|
||||
|
||||
static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_header, const Layer* layer, int child_level)
|
||||
static void ase_file_write_layer_chunk(FILE* f,
|
||||
const dio::AsepriteHeader* header,
|
||||
dio::AsepriteFrameHeader* frame_header,
|
||||
const Layer* layer,
|
||||
const int child_level)
|
||||
{
|
||||
ChunkWriter chunk(f, frame_header, ASE_FILE_CHUNK_LAYER);
|
||||
|
||||
|
|
@ -718,13 +733,21 @@ static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
|
|||
static_cast<int>(doc::LayerFlags::PersistentFlagsMask), f);
|
||||
|
||||
// Layer type
|
||||
bool saveBlendInfo = false;
|
||||
int layerType = ASE_FILE_LAYER_IMAGE;
|
||||
if (layer->isImage()) {
|
||||
saveBlendInfo = true;
|
||||
if (layer->isTilemap())
|
||||
layerType = ASE_FILE_LAYER_TILEMAP;
|
||||
}
|
||||
else if (layer->isGroup()) {
|
||||
layerType = ASE_FILE_LAYER_GROUP;
|
||||
|
||||
// If the "composite groups" flag is not specified, group layers
|
||||
// don't contain blend mode + opacity.
|
||||
if ((header->flags & ASE_FILE_FLAG_COMPOSITE_GROUPS) == ASE_FILE_FLAG_COMPOSITE_GROUPS) {
|
||||
saveBlendInfo = true;
|
||||
}
|
||||
}
|
||||
fputw(layerType, f);
|
||||
|
||||
|
|
@ -734,8 +757,8 @@ static void ase_file_write_layer_chunk(FILE* f, dio::AsepriteFrameHeader* frame_
|
|||
// Default width & height, and blend mode
|
||||
fputw(0, f);
|
||||
fputw(0, f);
|
||||
fputw(layer->isImage() ? (int)static_cast<const LayerImage*>(layer)->blendMode(): 0, f);
|
||||
fputc(layer->isImage() ? (int)static_cast<const LayerImage*>(layer)->opacity(): 0, f);
|
||||
fputw(saveBlendInfo ? int(layer->blendMode()): 0, f);
|
||||
fputc(saveBlendInfo ? int(layer->opacity()): 0, f);
|
||||
|
||||
// Padding
|
||||
ase_file_write_padding(f, 3);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
|
@ -26,6 +26,7 @@ void FileOpConfig::fillFromPreferences()
|
|||
workingCS = get_working_rgb_space_from_preferences();
|
||||
rgbMapAlgorithm = pref.quantization.rgbmapAlgorithm();
|
||||
cacheCompressedTilesets = pref.tileset.cacheCompressedTilesets();
|
||||
composeGroups = pref.experimental.composeGroups();
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
|
@ -42,6 +42,11 @@ namespace app {
|
|||
// compressed data that was loaded as-is).
|
||||
bool cacheCompressedTilesets = true;
|
||||
|
||||
// True if layer groups are composed in a separate image first,
|
||||
// and then composed with the rest of the sprite. In this case
|
||||
// blend mode and opacity fields are valid for groups too.
|
||||
bool composeGroups = false;
|
||||
|
||||
void fillFromPreferences();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2018-2023 Igara Studio S.A.
|
||||
Copyright (c) 2018-2024 Igara Studio S.A.
|
||||
Copyright (c) 2016-2018 David Capello
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#define ASE_FILE_FRAME_MAGIC 0xF1FA
|
||||
|
||||
#define ASE_FILE_FLAG_LAYER_WITH_OPACITY 1
|
||||
#define ASE_FILE_FLAG_COMPOSITE_GROUPS 2
|
||||
|
||||
#define ASE_FILE_CHUNK_FLI_COLOR2 4
|
||||
#define ASE_FILE_CHUNK_FLI_COLOR 11
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite Document IO Library
|
||||
// Copyright (c) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
|
|
@ -600,12 +600,14 @@ doc::Layer* AsepriteDecoder::readLayerChunk(AsepriteHeader* header,
|
|||
}
|
||||
|
||||
if (layer) {
|
||||
if (layer->isImage() &&
|
||||
const bool composeGroups = (header->flags & ASE_FILE_FLAG_COMPOSITE_GROUPS);
|
||||
|
||||
if ((layer->isImage() || (layer->isGroup() && composeGroups)) &&
|
||||
// Only transparent layers can have blend mode and opacity
|
||||
!(flags & int(doc::LayerFlags::Background))) {
|
||||
static_cast<doc::LayerImage*>(layer)->setBlendMode((doc::BlendMode)blendmode);
|
||||
layer->setBlendMode((doc::BlendMode)blendmode);
|
||||
if (header->flags & ASE_FILE_FLAG_LAYER_WITH_OPACITY)
|
||||
static_cast<doc::LayerImage*>(layer)->setOpacity(opacity);
|
||||
layer->setOpacity(opacity);
|
||||
}
|
||||
|
||||
// flags
|
||||
|
|
|
|||
Loading…
Reference in New Issue