Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0b4576d
Add Sai2 thumbnail extraction sample
Wunkolo Mar 1, 2025
8640adc
Init Sai2 header
Wunkolo Mar 1, 2025
d65cfc2
Fix Thumbnail-Sai2 translation unit
Wunkolo Mar 2, 2025
d16e3d5
Refactor Sai2 Canvas file types
Wunkolo Mar 2, 2025
4b2dd9f
Add `Tag{LE,BE}` specifiers
Wunkolo Mar 2, 2025
a2e2308
Update clang format
Wunkolo Mar 2, 2025
680fdda
Refactor RLE decompression iteration code
Wunkolo Mar 2, 2025
84c0a73
Fix tile iteration
Wunkolo Mar 2, 2025
a340d57
Fix signed/unsigned comparison
Wunkolo Mar 3, 2025
71cd3a1
Draft `DeltaUnpack16`
Wunkolo Mar 3, 2025
1f3960e
Implement delta-row decompression
Wunkolo Mar 3, 2025
a48d090
Fix tile dump file path type
Wunkolo Mar 3, 2025
df0a5c1
Add generic implementation of `UnpackDeltaRLE16`
Wunkolo Mar 3, 2025
9266f42
Fix big-endian tag values, add init "old" thumbnail format
Wunkolo Mar 3, 2025
00d33b4
Init Fssj data iteration
Wunkolo Mar 14, 2025
46a8db0
Add `up` PNG Filter finding
Wunkolo Mar 16, 2025
fc5258a
Remove unused BlockBuffer
Wunkolo Mar 16, 2025
a3c3779
Add sample `fssj` block observations
Wunkolo Mar 16, 2025
d3e6691
Update sai2 BlobData endian
Wunkolo Mar 22, 2025
b813f29
Refactor `FssJ`->`Jssf`
Wunkolo Mar 22, 2025
ca61a8e
Draft `DecompressFssjBlock`
Wunkolo Apr 26, 2025
9900e32
Refactor `ConvertJssfToRGB`
Wunkolo Apr 26, 2025
a055f66
Refactor `ConvertRGB8ToYUV16`
Wunkolo Apr 27, 2025
f650c8a
Add sai2 translation unit
Wunkolo Apr 27, 2025
c2c8962
Implement `IterateCanvasData`
Wunkolo Apr 27, 2025
2040b24
Refactor delta-compressed data into sai2 translation unit
Wunkolo Apr 27, 2025
31c7d4d
FIx double->float truncation
Wunkolo Apr 27, 2025
c5e6abc
Init `jssf` MCU decoding
Wunkolo Jul 10, 2025
2539cf2
Update project to C++23
Wunkolo Jul 11, 2025
d349920
Draft converting `jssf` into standard jpeg stream
Wunkolo Jul 11, 2025
0b3fc85
Add `stb_image`
Wunkolo Jul 11, 2025
d9eff3b
Draft direct jpeg output
Wunkolo Jul 11, 2025
60c16b4
Remove `ConvertRGB8ToYUV16`
Wunkolo Jul 11, 2025
a3ae3e4
Refactor for C++20 features
Wunkolo Jul 11, 2025
59b5924
Fix Huffman table interpretation
Wunkolo Jul 12, 2025
8c2da63
Add additional Huffman tables when thumbnail is color
Wunkolo Jul 12, 2025
8b500f8
Remove final `RSTm` marker
Wunkolo Jul 12, 2025
d72fc46
Refactor thumbnail extraction prototype
Wunkolo Jul 12, 2025
70425b4
Fix table entry printf FourCC type
Wunkolo Oct 20, 2025
bd7ea0b
Refactor Jssf->Jpeg conversion to `ExtractJssfToJpeg`
Wunkolo Oct 20, 2025
a8168d6
Refactor Jssf to Jpeg conversion into Sai2 library
Wunkolo Oct 20, 2025
dd47095
Quit at the first found supported thumbnail
Wunkolo Nov 16, 2025
9e66b2f
Refactor Jssf extraction routines into Sai2 library
Wunkolo Nov 16, 2025
d3ce3b6
Add missing `pragma once`
Wunkolo Nov 16, 2025
5e689df
Add additional canvas data tags
Wunkolo Nov 21, 2025
fbf9190
Add Canvas Background flag data
Wunkolo Nov 21, 2025
1624dc8
Add initial `layr` and `hist` structures
Wunkolo Nov 21, 2025
91171c5
Fix `dpcm` canvas background flag compile error
Wunkolo Nov 21, 2025
70dec9d
Add `raw` data tag
Wunkolo Nov 21, 2025
124c806
Refactor Delta-Encoded tile iteration
Wunkolo Nov 22, 2025
8fe26a7
Fix unaligned RLE bitstream support
Wunkolo Nov 22, 2025
3c85ea6
Utilize tile-size table for iteration
Wunkolo Nov 22, 2025
31aeaf8
Fix tile checksum iteration
Wunkolo Nov 22, 2025
4c74274
Fix Delta-Compressed indexing
Wunkolo Nov 22, 2025
bd962c0
Fix RLE bitstream iteration
Wunkolo Nov 22, 2025
9587608
Refactor `dpcm` extraction into sai2 library
Wunkolo Nov 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
Language: Cpp
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
AlignConsecutiveAssignments: true
Expand All @@ -21,19 +21,19 @@ BinPackArguments: true
BinPackParameters: true
BitFieldColonSpacing: Both
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
AfterStruct: true
AfterUnion: true
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
Expand All @@ -42,22 +42,22 @@ BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakStringLiterals: true
ColumnLimit: 100
ColumnLimit: 80
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
IndentCaseLabels: false
IndentWidth: 4
IndentWidth: 4
IndentWrappedFunctionNames: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ""
MacroBlockEnd: ""
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 4
Expand All @@ -68,21 +68,22 @@ PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: Never
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInConditionalStatement: true
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: ForContinuationAndIndentation
Standard: c++20
TabWidth: 4
UseTab: ForContinuationAndIndentation
...
22 changes: 18 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ add_library(
source/ifstreambuf.cpp
source/keys.cpp
source/sai.cpp
source/sai2.cpp
source/virtualfileentry.cpp
source/virtualfilesystem.cpp
source/virtualpage.cpp
Expand All @@ -93,15 +94,28 @@ target_include_directories(
)

if( MASTER_PROJECT )
### Decryption sample
### Thumbnail-Sai1 sample
add_executable(
Thumbnail
samples/Thumbnail.cpp
Thumbnail-Sai1
samples/Thumbnail-Sai1.cpp
samples/stb_image_write.c
)
target_link_libraries(
Thumbnail-Sai1
PRIVATE
sai
)


### Thumbnail-Sai2 sample
add_executable(
Thumbnail-Sai2
samples/Thumbnail-Sai2.cpp
samples/stb_image_write.c
samples/stb_image.c
)
target_link_libraries(
Thumbnail
Thumbnail-Sai2
PRIVATE
sai
)
Expand Down
21 changes: 2 additions & 19 deletions include/sai.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#pragma once
#include <array>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <ctime>
Expand All @@ -15,26 +14,10 @@
#include <span>
#include <tuple>

namespace sai
{
#include "util.hpp"

template<std::endian Endianness = std::endian::big, std::size_t N>
inline constexpr std::uint32_t Tag(const char (&TagString)[N])
namespace sai
{
static_assert(N == 5, "Tag must be 4 characters");
if constexpr( Endianness == std::endian::big )
{
return (
(TagString[3] << 0) | (TagString[2] << 8) | (TagString[1] << 16) | (TagString[0] << 24)
);
}
else
{
return (
(TagString[3] << 24) | (TagString[2] << 16) | (TagString[1] << 8) | (TagString[0] << 0)
);
}
}

enum class LayerType
{
Expand Down
154 changes: 154 additions & 0 deletions include/sai2.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 Wunkolo
// SPDX-License-Identifier: MIT

#pragma once

#include <array>
#include <cstdint>
#include <functional>
#include <span>

#include "util.hpp"

namespace sai2
{
using namespace sai;

struct CanvasHeader
{
// "SAI-CANVAS-TYPE0"
std::array<char, 16> Identifier;

std::uint8_t Flags0;

// & 0x7 seems to indicate if thumbnail has transparency
std::uint8_t CanvasBackgroundFlags;

std::uint8_t Flags2;
std::uint8_t Flags3;

std::uint32_t Width;
std::uint32_t Height;
std::uint32_t PrintingResolution;
std::uint32_t TableCount;
std::uint32_t SelectedLayer;
std::uint64_t UnknownA;
std::uint64_t UnknownB;
std::uint32_t CanvasBackgroundColor;

// "norm"/"vivd"/"deep"/"mult"/"ver1"
std::uint32_t LayerEffectColor;
};
static_assert(sizeof(CanvasHeader) == 64);

enum class CanvasDataType : std::uint32_t
{
// History data
History = TagLE("hist"),

// jpeg-encoded thumbnails
ThumbnailLossy = TagLE("thum"),
// delta-compressed thumbnails
ThumbnailLossless = TagLE("intg"),

// Layer descriptor
Layer = TagLE("layr"),
// Standalone mask not associated with a layer
// such as with the selection-pen(the "blue" mask)
Mask = TagLE("mask"),
// Layer data
LayerPixels = TagLE("lpix"),
FPixels = TagLE("fpix"), // Folder pixels?
MaskPixels = TagLE("mpix"),
LayerLinework = TagLE("liwk"),
Shape = TagLE("shap"),
// Rulers/guides
Perspective = TagLE("pers"),
Text = TagLE("text"),
Grid = TagLE("grid"),
Symmetry = TagLE("symm"),
};

struct CanvasEntry
{
CanvasDataType Type;
std::uint32_t LayerID;
std::uint64_t BlobsOffset; // Absolute file offset
};
static_assert(sizeof(CanvasEntry) == 16);

// Canvases 512x512 or larger will use the jssf format, smaller canvases will
// use the delta-compressed format
enum class BlobDataType : std::uint32_t
{
// Raw
Raw = TagLE("raw\0"),
// Delta-compressed pixel stream with an additional "RLE" compression
DeltaPixelsCompressed = TagLE("dpcm"),
// Older image format used for thumbnails, based on JPEG
Jssf = TagLE("jssf"),
};

struct BlobLayr
{
std::uint32_t LayerID;
std::uint32_t Unknown;
std::uint32_t UnknownOne;
std::uint32_t BlendingMode;
std::uint32_t UnknownB;
std::uint32_t UnknownC;
std::uint32_t UnknownD;
std::uint32_t UnknownE;
std::uint32_t UnknownF;
std::uint32_t UnknownG;
std::uint32_t MaskBlendingMode;
std::uint32_t UnknownI;
std::uint32_t UnknownJ;
};

struct BlobHist
{
char16_t HistIdentifier[32];
char8_t HistHash[16];
std::uint32_t Revision;
};

// Return false to break iteration
using CanvasDataProcT = bool(
const sai2::CanvasHeader& Header, const sai2::CanvasEntry& TableEntry,
std::span<const std::byte> Bytes
);

bool IterateCanvasData(
const std::span<const std::byte> FileData,
const std::function<CanvasDataProcT> CanvasDataProc
);

std::size_t UnpackDeltaRLE16(
std::span<const std::byte> Compressed, std::span<std::int16_t> Decompressed,
std::uint32_t PixelCount, std::uint8_t OutputChannels,
std::uint8_t InputChannels
);

// This looks kind of like the "Up" png-filter seen here:
// http://www.libpng.org/pub/png/spec/1.2/PNG-Filters.html
uint32_t DeltaUnpackRow16Bpc(
uint32_t* Dest8Bpc, const std::uint32_t* PreviousRow8Bpc,
const std::uint64_t* DeltaEncoded16Bpc, const std::uint32_t PixelCount
);

// Extracts a JSSF table entry into a standard jpeg stream
// Returns (Jpeg Data, Width, Height).
// Returns (null,0,0) if an error has occurred.
std::tuple<std::vector<std::byte>, std::uint32_t, std::uint32_t>
ExtractJssfToJpeg(std::span<const std::byte> JssfTableData);

// Extracts a DPCM table entry into BGRA pixels
// Returns (BGRA Data, Width, Height).
// Returns (null,0,0) if an error has occurred.
std::tuple<std::vector<std::byte>, std::uint32_t, std::uint32_t>
ExtractDpcmToBGRA(
const CanvasHeader& Header, std::span<const std::byte> JssfTableData
);

} // namespace sai2
41 changes: 41 additions & 0 deletions include/util.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: Copyright (c) 2025 Wunkolo
// SPDX-License-Identifier: MIT
#pragma once

#include <bit>
#include <cstdint>

namespace sai
{

template<std::endian Endianness = std::endian::big, std::size_t N>
inline constexpr std::uint32_t Tag(const char (&TagString)[N])
{
static_assert(N == 5, "Tag must be 4 characters");
if constexpr( Endianness == std::endian::big )
{
return (
(TagString[3] << 0) | (TagString[2] << 8) | (TagString[1] << 16)
| (TagString[0] << 24)
);
}
else
{
return (
(TagString[3] << 24) | (TagString[2] << 16) | (TagString[1] << 8)
| (TagString[0] << 0)
);
}
}

template<std::size_t N>
inline constexpr std::uint32_t TagLE(const char (&TagString)[N])
{
return Tag<std::endian::little, N>(TagString);
}
template<std::size_t N>
inline constexpr std::uint32_t TagBE(const char (&TagString)[N])
{
return Tag<std::endian::big, N>(TagString);
}
} // namespace sai
File renamed without changes.
Loading