From 3e90df5ef47f6ecb5814f503529bc0c3823f875a Mon Sep 17 00:00:00 2001 From: Sour Date: Sun, 9 May 2021 23:14:30 -0400 Subject: [PATCH] WIP HD packs --- Core/Core.vcxproj | 15 + Core/Core.vcxproj.filters | 15 + Core/NES/DefaultNesPpu.h | 10 +- Core/NES/HdPacks/HdAudioDevice.cpp | 183 + Core/NES/HdPacks/HdAudioDevice.h | 37 + Core/NES/HdPacks/HdData.h | 382 ++ Core/NES/HdPacks/HdNesPack.cpp | 423 +++ Core/NES/HdPacks/HdNesPack.h | 61 + Core/NES/HdPacks/HdNesPpu.cpp | 51 + Core/NES/HdPacks/HdNesPpu.h | 164 +- Core/NES/HdPacks/HdPackConditions.h | 301 ++ Core/NES/HdPacks/HdPackLoader.cpp | 745 ++++ Core/NES/HdPacks/HdPackLoader.h | 50 + Core/NES/HdPacks/HdVideoFilter.cpp | 38 + Core/NES/HdPacks/HdVideoFilter.h | 21 + Core/NES/HdPacks/OggMixer.cpp | 121 + Core/NES/HdPacks/OggMixer.h | 37 + Core/NES/HdPacks/OggReader.cpp | 94 + Core/NES/HdPacks/OggReader.h | 36 + Core/NES/NesConsole.cpp | 57 +- Core/NES/NesConsole.h | 6 + Core/NES/NesPpu.cpp | 21 +- Core/NES/NesPpu.h | 2 +- Core/NES/NesSoundMixer.cpp | 26 +- Core/NES/NesSoundMixer.h | 5 - Core/NES/NsfPpu.h | 5 + Core/Shared/SaveStateManager.cpp | 2 +- Core/Shared/Video/BaseVideoFilter.cpp | 3 +- Core/Shared/Video/BaseVideoFilter.h | 11 +- Core/Shared/Video/DebugHud.cpp | 4 +- Core/Shared/Video/DebugHud.h | 2 +- Core/Shared/Video/DrawCommand.h | 33 +- Core/Shared/Video/VideoDecoder.cpp | 12 +- Core/Shared/Video/VideoDecoder.h | 3 +- Core/Shared/Video/VideoRenderer.cpp | 1 - Utilities/PNGHelper.cpp | 570 +-- Utilities/Utilities.vcxproj | 27 +- Utilities/Utilities.vcxproj.filters | 4 + Utilities/miniz.h | 1 - Utilities/spng.c | 4778 +++++++++++++++++++++++++ Utilities/spng.h | 618 ++++ 41 files changed, 8344 insertions(+), 631 deletions(-) create mode 100644 Core/NES/HdPacks/HdAudioDevice.cpp create mode 100644 Core/NES/HdPacks/HdAudioDevice.h create mode 100644 Core/NES/HdPacks/HdData.h create mode 100644 Core/NES/HdPacks/HdNesPack.cpp create mode 100644 Core/NES/HdPacks/HdNesPack.h create mode 100644 Core/NES/HdPacks/HdNesPpu.cpp create mode 100644 Core/NES/HdPacks/HdPackConditions.h create mode 100644 Core/NES/HdPacks/HdPackLoader.cpp create mode 100644 Core/NES/HdPacks/HdPackLoader.h create mode 100644 Core/NES/HdPacks/HdVideoFilter.cpp create mode 100644 Core/NES/HdPacks/HdVideoFilter.h create mode 100644 Core/NES/HdPacks/OggMixer.cpp create mode 100644 Core/NES/HdPacks/OggMixer.h create mode 100644 Core/NES/HdPacks/OggReader.cpp create mode 100644 Core/NES/HdPacks/OggReader.h create mode 100644 Utilities/spng.c create mode 100644 Utilities/spng.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 6938d7b6..d89c7e45 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -48,7 +48,15 @@ + + + + + + + + @@ -362,6 +370,13 @@ + + + + + + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 01bda211..9fc64d57 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -791,6 +791,14 @@ + + + + + + + + @@ -1217,6 +1225,13 @@ + + + + + + + diff --git a/Core/NES/DefaultNesPpu.h b/Core/NES/DefaultNesPpu.h index c13c2a6a..73d423e5 100644 --- a/Core/NES/DefaultNesPpu.h +++ b/Core/NES/DefaultNesPpu.h @@ -9,13 +9,9 @@ public: { } - __forceinline void StoreSpriteAbsoluteAddress() - { - } - - __forceinline void StoreTileAbsoluteAddress() - { - } + __forceinline void StoreSpriteInformation(bool verticalMirror, uint16_t tileAddr, uint8_t lineOffset) { } + __forceinline void StoreTileInformation() { } + void* OnBeforeSendFrame() { return nullptr; } __forceinline void ProcessScanline() { diff --git a/Core/NES/HdPacks/HdAudioDevice.cpp b/Core/NES/HdPacks/HdAudioDevice.cpp new file mode 100644 index 00000000..aa8f8cc8 --- /dev/null +++ b/Core/NES/HdPacks/HdAudioDevice.cpp @@ -0,0 +1,183 @@ +#include "stdafx.h" +#include "NES/HdPacks/HdAudioDevice.h" +#include "NES/HdPacks/HdData.h" +#include "NES/HdPacks/OggMixer.h" +#include "NES/NesConsole.h" +#include "Shared/MessageManager.h" +#include "Shared/Emulator.h" +#include "Shared/Audio/SoundMixer.h" +#include "Utilities/Serializer.h" + +HdAudioDevice::HdAudioDevice(Emulator* emu, HdPackData* hdData) +{ + _emu = emu; + _hdData = hdData; + _album = 0; + _playbackOptions = 0; + _trackError = false; + _sfxVolume = 128; + _bgmVolume = 128; + + _oggMixer.reset(new OggMixer()); + _oggMixer->SetBgmVolume(_bgmVolume); + _oggMixer->SetSfxVolume(_sfxVolume); + _emu->GetSoundMixer()->RegisterAudioProvider(_oggMixer.get()); +} + +HdAudioDevice::~HdAudioDevice() +{ + _emu->GetSoundMixer()->UnregisterAudioProvider(_oggMixer.get()); +} + +void HdAudioDevice::Serialize(Serializer& s) +{ + int32_t trackOffset = 0; + if(s.IsSaving()) { + trackOffset = _oggMixer->GetBgmOffset(); + if(trackOffset < 0) { + _lastBgmTrack = -1; + } + s.Stream(_album, _lastBgmTrack, trackOffset, _sfxVolume, _bgmVolume, _playbackOptions); + } else { + s.Stream(_album, _lastBgmTrack, trackOffset, _sfxVolume, _bgmVolume, _playbackOptions); + if(_lastBgmTrack != -1 && trackOffset > 0) { + PlayBgmTrack(_lastBgmTrack, trackOffset); + } + _oggMixer->SetBgmVolume(_bgmVolume); + _oggMixer->SetSfxVolume(_sfxVolume); + _oggMixer->SetPlaybackOptions(_playbackOptions); + } +} + +bool HdAudioDevice::PlayBgmTrack(uint8_t track, uint32_t startOffset) +{ + int trackId = _album * 256 + track; + auto result = _hdData->BgmFilesById.find(trackId); + if(result != _hdData->BgmFilesById.end()) { + if(_oggMixer->Play(result->second, false, startOffset)) { + _lastBgmTrack = trackId; + return true; + } + } else { + MessageManager::Log("[HDPack] Invalid album+track combination: " + std::to_string(_album) + ":" + std::to_string(track)); + } + return false; +} + +bool HdAudioDevice::PlaySfx(uint8_t sfxNumber) +{ + auto result = _hdData->SfxFilesById.find(_album * 256 + sfxNumber); + if(result != _hdData->SfxFilesById.end()) { + return !_oggMixer->Play(result->second, true, 0); + } else { + MessageManager::Log("[HDPack] Invalid album+sfx number combination: " + std::to_string(_album) + ":" + std::to_string(sfxNumber)); + return false; + } +} + +void HdAudioDevice::ProcessControlFlags(uint8_t flags) +{ + _oggMixer->SetPausedFlag((flags & 0x01) == 0x01); + if(flags & 0x02) { + _oggMixer->StopBgm(); + } + if(flags & 0x04) { + _oggMixer->StopSfx(); + } +} + +void HdAudioDevice::GetMemoryRanges(MemoryRanges & ranges) +{ + bool useAlternateRegisters = (_hdData->OptionFlags & (int)HdPackOptions::AlternateRegisterRange) == (int)HdPackOptions::AlternateRegisterRange; + ranges.SetAllowOverride(); + + if(useAlternateRegisters) { + for(int i = 0; i < 7; i++) { + ranges.AddHandler(MemoryOperation::Write, 0x3002 + i * 0x10); + } + ranges.AddHandler(MemoryOperation::Read, 0x4018); + ranges.AddHandler(MemoryOperation::Read, 0x4019); + } else { + ranges.AddHandler(MemoryOperation::Any, 0x4100, 0x4106); + } +} + +void HdAudioDevice::WriteRam(uint16_t addr, uint8_t value) +{ + //$4100/$3002: Playback Options + //$4101/$3012: Playback Control + //$4102/$3022: BGM Volume + //$4103/$3032: SFX Volume + //$4104/$3042: Album Number + //$4105/$3052: Play BGM Track + //$4106/$3062: Play SFX Track + int regNumber = addr > 0x4100 ? (addr & 0xF) : ((addr & 0xF0) >> 4); + + switch(regNumber) { + //Playback Options + //Bit 0: Loop BGM + //Bit 1-7: Unused, reserved - must be 0 + case 0: + _playbackOptions = value; + _oggMixer->SetPlaybackOptions(_playbackOptions); + break; + + //Playback Control + //Bit 0: Toggle Pause/Resume (only affects BGM) + //Bit 1: Stop BGM + //Bit 2: Stop all SFX + //Bit 3-7: Unused, reserved - must be 0 + case 1: ProcessControlFlags(value); break; + + //BGM Volume: 0 = mute, 255 = max + //Also has an immediate effect on currently playing BGM + case 2: + _bgmVolume = value; + _oggMixer->SetBgmVolume(value); + break; + + //SFX Volume: 0 = mute, 255 = max + //Also has an immediate effect on all currently playing SFX + case 3: + _sfxVolume = value; + _oggMixer->SetSfxVolume(value); + break; + + //Album number: 0-255 (Allows for up to 64k BGM and SFX tracks) + //No immediate effect - only affects subsequent $4FFE/$4FFF writes + case 4: _album = value; break; + + //Play BGM track (0-255 = track number) + //Stop the current BGM and starts a new track + case 5: _trackError = PlayBgmTrack(value, 0); break; + + //Play sound effect (0-255 = sfx number) + //Plays a new sound effect (no limit to the number of simultaneous sound effects) + case 6: _trackError = PlaySfx(value); break; + } +} + +uint8_t HdAudioDevice::ReadRam(uint16_t addr) +{ + //$4100/$4018: Status + //$4101/$4019: Revision + //$4102: 'N' (signature to help detection) + //$4103: 'E' + //$4103: 'A' + switch(addr & 0x7) { + case 0: + //Status + return ( + (_oggMixer->IsBgmPlaying() ? 1 : 0) | + (_oggMixer->IsSfxPlaying() ? 2 : 0) | + (_trackError ? 4 : 0) + ); + + case 1: return 1; //Revision + case 2: return 'N'; //NES + case 3: return 'E'; //Enhanced + case 4: return 'A'; //Audio + } + + return 0; +} diff --git a/Core/NES/HdPacks/HdAudioDevice.h b/Core/NES/HdPacks/HdAudioDevice.h new file mode 100644 index 00000000..b05f9e7c --- /dev/null +++ b/Core/NES/HdPacks/HdAudioDevice.h @@ -0,0 +1,37 @@ +#pragma once +#include "stdafx.h" +#include "NES/INesMemoryHandler.h" +#include "Utilities/ISerializable.h" + +struct HdPackData; +class Emulator; +class OggMixer; + +class HdAudioDevice : public INesMemoryHandler, public ISerializable +{ +private: + Emulator* _emu = nullptr; + HdPackData* _hdData = nullptr; + uint8_t _album = 0; + uint8_t _playbackOptions = 0; + bool _trackError = false; + unique_ptr _oggMixer; + int32_t _lastBgmTrack = 0; + uint8_t _bgmVolume = 0; + uint8_t _sfxVolume = 0; + + bool PlayBgmTrack(uint8_t track, uint32_t startOffset); + bool PlaySfx(uint8_t sfxNumber); + void ProcessControlFlags(uint8_t flags); + +protected: + void Serialize(Serializer& s) override; + +public: + HdAudioDevice(Emulator* emu, HdPackData *hdData); + ~HdAudioDevice(); + + void GetMemoryRanges(MemoryRanges &ranges) override; + void WriteRam(uint16_t addr, uint8_t value) override; + uint8_t ReadRam(uint16_t addr) override; +}; \ No newline at end of file diff --git a/Core/NES/HdPacks/HdData.h b/Core/NES/HdPacks/HdData.h new file mode 100644 index 00000000..2024782e --- /dev/null +++ b/Core/NES/HdPacks/HdData.h @@ -0,0 +1,382 @@ +#pragma once +#include "stdafx.h" +#include "NES/NesConstants.h" +#include "Utilities/HexUtilities.h" + +struct HdTileKey +{ + static constexpr int32_t NoTile = -1; + + uint32_t PaletteColors; + uint8_t TileData[16]; + int32_t TileIndex; + bool IsChrRamTile = false; + + HdTileKey GetKey(bool defaultKey) + { + if(defaultKey) { + HdTileKey copy = *this; + copy.PaletteColors = 0xFFFFFFFF; + return copy; + } else { + return *this; + } + } + + uint32_t GetHashCode() const + { + if(IsChrRamTile) { + return CalculateHash((uint8_t*)&PaletteColors, 20); + } else { + uint64_t key = TileIndex | ((uint64_t)PaletteColors << 32); + return CalculateHash((uint8_t*)&key, sizeof(key)); + } + } + + size_t operator() (const HdTileKey &tile) const { + return tile.GetHashCode(); + } + + bool operator==(const HdTileKey &other) const + { + if(IsChrRamTile) { + return memcmp((uint8_t*)&PaletteColors, (uint8_t*)&other.PaletteColors, 20) == 0; + } else { + return TileIndex == other.TileIndex && PaletteColors == other.PaletteColors; + } + } + + uint32_t CalculateHash(const uint8_t* key, size_t len) const + { + uint32_t result = 0; + for(size_t i = 0; i < len; i += 4) { + uint32_t chunk; + memcpy(&chunk, key, sizeof(uint32_t)); + + result += chunk; + result = (result << 2) | (result >> 30); + key += 4; + } + return result; + } + + bool IsSpriteTile() + { + return (PaletteColors & 0xFF000000) == 0xFF000000; + } +}; + +namespace std { + template <> struct hash + { + size_t operator()(const HdTileKey& x) const + { + return x.GetHashCode(); + } + }; +} + +struct HdPpuTileInfo : public HdTileKey +{ + uint8_t OffsetX; + uint8_t OffsetY; + bool HorizontalMirroring; + bool VerticalMirroring; + bool BackgroundPriority; + + uint8_t BgColorIndex; + uint8_t SpriteColorIndex; + uint8_t BgColor; + uint8_t SpriteColor; + uint8_t PpuBackgroundColor; +}; + +struct HdPpuPixelInfo +{ + HdPpuTileInfo Tile; + vector Sprite; + int SpriteCount; + + uint16_t TmpVideoRamAddr; + uint8_t XScroll; + uint8_t EmphasisBits; + bool Grayscale; + + HdPpuPixelInfo() + { + for(int i = 0; i < 4; i++) { + Sprite.push_back(HdPpuTileInfo()); + } + } +}; + +struct HdScreenInfo +{ + HdPpuPixelInfo* ScreenTiles; + std::unordered_map WatchedAddressValues; + uint32_t FrameNumber; + + HdScreenInfo(const HdScreenInfo& that) = delete; + + HdScreenInfo(bool isChrRamGame) + { + ScreenTiles = new HdPpuPixelInfo[NesConstants::ScreenPixelCount]; + + for(int i = 0; i < NesConstants::ScreenPixelCount; i++) { + ScreenTiles[i].Tile.BackgroundPriority = false; + ScreenTiles[i].Tile.IsChrRamTile = isChrRamGame; + ScreenTiles[i].Tile.HorizontalMirroring = false; + ScreenTiles[i].Tile.VerticalMirroring = false; + + for(int j = 0; j < 4; j++) { + ScreenTiles[i].Sprite[j].IsChrRamTile = isChrRamGame; + } + } + } + + ~HdScreenInfo() + { + delete[] ScreenTiles; + } +}; + +struct HdPackCondition +{ + string Name; + + virtual string GetConditionName() = 0; + virtual bool IsExcludedFromFile() { return Name.size() > 0 && Name[0] == '!'; } + virtual string ToString() = 0; + + virtual ~HdPackCondition() { } + + void ClearCache() + { + _resultCache = -1; + } + + bool CheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) + { + if(_resultCache == -1) { + bool result = InternalCheckCondition(screenInfo, x, y, tile); + if(Name[0] == '!') { + result = !result; + } + + if(_useCache) { + _resultCache = result ? 1 : 0; + } + return result; + } else { + return (bool)_resultCache; + } + } + +protected: + int8_t _resultCache = -1; + bool _useCache = false; + + virtual bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) = 0; +}; + +struct HdPackTileInfo : public HdTileKey +{ + uint32_t X; + uint32_t Y; + uint32_t BitmapIndex; + int Brightness; + bool DefaultTile; + bool Blank; + bool HasTransparentPixels; + bool TransparencyRequired; + bool IsFullyTransparent; + vector HdTileData; + uint32_t ChrBankId; + + vector Conditions; + bool ForceDisableCache; + + bool MatchesCondition(HdScreenInfo *hdScreenInfo, int x, int y, HdPpuTileInfo* tile) + { + for(HdPackCondition* condition : Conditions) { + if(!condition->CheckCondition(hdScreenInfo, x, y, tile)) { + return false; + } + } + return true; + } + + vector ToRgb(uint32_t* palette) + { + vector rgbBuffer; + for(uint8_t i = 0; i < 8; i++) { + uint8_t lowByte = TileData[i]; + uint8_t highByte = TileData[i + 8]; + for(uint8_t j = 0; j < 8; j++) { + uint8_t color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1); + uint32_t rgbColor; + if(IsSpriteTile() || TransparencyRequired) { + rgbColor = color == 0 ? 0x00FFFFFF : palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F]; + } else { + rgbColor = palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F]; + } + rgbBuffer.push_back(rgbColor); + } + } + + return rgbBuffer; + } + + void UpdateFlags() + { + Blank = true; + HasTransparentPixels = false; + IsFullyTransparent = true; + for(size_t i = 0; i < HdTileData.size(); i++) { + if(HdTileData[i] != HdTileData[0]) { + Blank = false; + } + if((HdTileData[i] & 0xFF000000) != 0xFF000000) { + HasTransparentPixels = true; + } + if(HdTileData[i] & 0xFF000000) { + IsFullyTransparent = false; + } + } + } + + string ToString(int pngIndex) + { + stringstream out; + + if(Conditions.size() > 0) { + out << "["; + for(size_t i = 0; i < Conditions.size(); i++) { + if(i > 0) { + out << "&"; + } + out << Conditions[i]->Name; + } + out << "]"; + } + + if(IsChrRamTile) { + out << "" << pngIndex << ","; + + for(int i = 0; i < 16; i++) { + out << HexUtilities::ToHex(TileData[i]); + } + out << "," << + HexUtilities::ToHex(PaletteColors, true) << "," << + X << "," << + Y << "," << + (double)Brightness / 255 << "," << + (DefaultTile ? "Y" : "N") << "," << + ChrBankId << "," << + TileIndex; + } else { + out << "" << + pngIndex << "," << + HexUtilities::ToHex(TileIndex) << "," << + HexUtilities::ToHex(PaletteColors, true) << "," << + X << "," << + Y << "," << + (double)Brightness / 255 << "," << + (DefaultTile ? "Y" : "N"); + } + + return out.str(); + } +}; + +struct HdPackBitmapInfo +{ + vector PixelData; + uint32_t Width; + uint32_t Height; +}; + +struct HdBackgroundFileData +{ + string PngName; + uint32_t Width; + uint32_t Height; + + vector PixelData; +}; + +struct HdBackgroundInfo +{ + HdBackgroundFileData* Data; + int Brightness; + vector Conditions; + float HorizontalScrollRatio; + float VerticalScrollRatio; + uint8_t Priority; + + uint32_t Left; + uint32_t Top; + + uint32_t* data() + { + return Data->PixelData.data(); + } + + string ToString() + { + stringstream out; + + if(Conditions.size() > 0) { + out << "["; + for(size_t i = 0; i < Conditions.size(); i++) { + if(i > 0) { + out << "&"; + } + out << Conditions[i]->Name; + } + out << "]"; + } + + out << ""; + out << Data->PngName << ","; + out << (Brightness / 255.0); + + return out.str(); + } +}; + +struct HdPackData +{ + vector Backgrounds; + vector> BackgroundFileData; + vector> Tiles; + vector> Conditions; + unordered_set WatchedMemoryAddresses; + unordered_map> TileByKey; + unordered_map PatchesByHash; + unordered_map BgmFilesById; + unordered_map SfxFilesById; + vector Palette; + + bool HasOverscanConfig = false; + OverscanDimensions Overscan; + + uint32_t Scale = 1; + uint32_t Version = 0; + uint32_t OptionFlags = 0; + + HdPackData() { } + ~HdPackData() { } + + HdPackData(const HdPackData&) = delete; + HdPackData& operator=(const HdPackData&) = delete; +}; + +enum class HdPackOptions +{ + None = 0, + NoSpriteLimit = 1, + AlternateRegisterRange = 2, + DisableCache = 8, + DontRenderOriginalTiles = 16 +}; \ No newline at end of file diff --git a/Core/NES/HdPacks/HdNesPack.cpp b/Core/NES/HdPacks/HdNesPack.cpp new file mode 100644 index 00000000..1e2b4378 --- /dev/null +++ b/Core/NES/HdPacks/HdNesPack.cpp @@ -0,0 +1,423 @@ +#include "stdafx.h" +#include +#include +#include "NES/HdPacks/HdNesPack.h" +#include "NES/HdPacks/HdPackLoader.h" +#include "NES/NesConsole.h" +#include "Shared/MessageManager.h" +#include "Shared/EmuSettings.h" +#include "Utilities/FolderUtilities.h" +#include "Utilities/PNGHelper.h" + +HdNesPack::HdNesPack(HdPackData* hdData) +{ + _hdData = hdData; +} + +HdNesPack::~HdNesPack() +{ +} + +void HdNesPack::BlendColors(uint8_t output[4], uint8_t input[4]) +{ + uint8_t invertedAlpha = 256 - input[3]; + output[0] = input[0] + (uint8_t)((invertedAlpha * output[0]) >> 8); + output[1] = input[1] + (uint8_t)((invertedAlpha * output[1]) >> 8); + output[2] = input[2] + (uint8_t)((invertedAlpha * output[2]) >> 8); + output[3] = 0xFF; +} + +uint32_t HdNesPack::AdjustBrightness(uint8_t input[4], int brightness) +{ + return ( + std::min(255, (brightness * ((int)input[0] + 1)) >> 8) | + (std::min(255, (brightness * ((int)input[1] + 1)) >> 8) << 8) | + (std::min(255, (brightness * ((int)input[2] + 1)) >> 8) << 16) | + (input[3] << 24) + ); +} + +void HdNesPack::DrawColor(uint32_t color, uint32_t *outputBuffer, uint32_t scale, uint32_t screenWidth) +{ + if(scale == 1) { + *outputBuffer = color; + } else { + for(uint32_t i = 0; i < scale; i++) { + std::fill(outputBuffer, outputBuffer + scale, color); + outputBuffer += screenWidth; + } + } +} + +void HdNesPack::DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth) +{ + int brightness = bgInfo.Brightness; + uint32_t left = bgInfo.Left; + uint32_t top = bgInfo.Top; + uint32_t width = bgInfo.Data->Width; + uint32_t *pngData = bgInfo.data() + ((top + y) * _hdData->Scale * width) + ((left + x) * _hdData->Scale); + uint32_t pixelColor; + + for(uint32_t i = 0; i < scale; i++) { + for(uint32_t j = 0; j < scale; j++) { + if(brightness == 255) { + pixelColor = *pngData; + } else { + pixelColor = AdjustBrightness((uint8_t*)pngData, brightness); + } + + if(((uint8_t*)pngData)[3] == 0xFF) { + *outputBuffer = pixelColor; + } else if(((uint8_t*)pngData)[3]) { + BlendColors((uint8_t*)outputBuffer, (uint8_t*)(&pixelColor)); + } + + outputBuffer++; + pngData++; + } + outputBuffer += screenWidth - scale; + pngData += width - scale; + } +} + +void HdNesPack::DrawTile(HdPpuTileInfo &tileInfo, HdPackTileInfo &hdPackTileInfo, uint32_t *outputBuffer, uint32_t screenWidth) +{ + if(hdPackTileInfo.IsFullyTransparent) { + return; + } + + uint32_t scale = GetScale(); + uint32_t *bitmapData = hdPackTileInfo.HdTileData.data(); + uint32_t tileWidth = 8 * scale; + uint8_t tileOffsetX = tileInfo.HorizontalMirroring ? 7 - tileInfo.OffsetX : tileInfo.OffsetX; + uint32_t bitmapOffset = (tileInfo.OffsetY * scale) * tileWidth + tileOffsetX * scale; + int32_t bitmapSmallInc = 1; + int32_t bitmapLargeInc = tileWidth - scale; + if(tileInfo.HorizontalMirroring) { + bitmapOffset += scale - 1; + bitmapSmallInc = -1; + bitmapLargeInc = tileWidth + scale; + } + if(tileInfo.VerticalMirroring) { + bitmapOffset += tileWidth * (scale - 1); + bitmapLargeInc = (tileInfo.HorizontalMirroring ? (int32_t)scale : -(int32_t)scale) - (int32_t)tileWidth; + } + + uint32_t rgbValue; + if(hdPackTileInfo.HasTransparentPixels || hdPackTileInfo.Brightness != 255) { + for(uint32_t y = 0; y < scale; y++) { + for(uint32_t x = 0; x < scale; x++) { + if(hdPackTileInfo.Brightness == 255) { + rgbValue = *(bitmapData + bitmapOffset); + } else { + rgbValue = AdjustBrightness((uint8_t*)(bitmapData + bitmapOffset), hdPackTileInfo.Brightness); + } + + if(!hdPackTileInfo.HasTransparentPixels || (bitmapData[bitmapOffset] & 0xFF000000) == 0xFF000000) { + *outputBuffer = rgbValue; + } else { + if(bitmapData[bitmapOffset] & 0xFF000000) { + BlendColors((uint8_t*)outputBuffer, (uint8_t*)&rgbValue); + } + } + outputBuffer++; + bitmapOffset += bitmapSmallInc; + } + bitmapOffset += bitmapLargeInc; + outputBuffer += screenWidth - scale; + } + } else { + for(uint32_t y = 0; y < scale; y++) { + for(uint32_t x = 0; x < scale; x++) { + *outputBuffer = *(bitmapData + bitmapOffset); + outputBuffer++; + bitmapOffset += bitmapSmallInc; + } + bitmapOffset += bitmapLargeInc; + outputBuffer += screenWidth - scale; + } + } +} + +uint32_t HdNesPack::GetScale() +{ + return _hdData->Scale; +} + +void HdNesPack::OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y) +{ + _scrollX = ((lineFirstPixel.TmpVideoRamAddr & 0x1F) << 3) | lineFirstPixel.XScroll | ((lineFirstPixel.TmpVideoRamAddr & 0x400) ? 0x100 : 0); + _useCachedTile = false; + + int32_t scrollY = (((lineFirstPixel.TmpVideoRamAddr & 0x3E0) >> 2) | ((lineFirstPixel.TmpVideoRamAddr & 0x7000) >> 12)) + ((lineFirstPixel.TmpVideoRamAddr & 0x800) ? 240 : 0); + + for(int layer = 0; layer < 4; layer++) { + for(int i = 0; i < _activeBgCount[layer]; i++) { + HdBgConfig& cfg = _bgConfig[layer * HdNesPack::PriorityLevelsPerLayer + i]; + HdBackgroundInfo& bgInfo = _hdData->Backgrounds[cfg.BackgroundIndex]; + cfg.BgScrollX = (int32_t)(_scrollX * bgInfo.HorizontalScrollRatio); + cfg.BgScrollY = (int32_t)(scrollY * bgInfo.VerticalScrollRatio); + if(y >= -cfg.BgScrollY && (y + bgInfo.Top + cfg.BgScrollY + 1) * _hdData->Scale <= bgInfo.Data->Height) { + cfg.BgMinX = -cfg.BgScrollX; + cfg.BgMaxX = bgInfo.Data->Width / _hdData->Scale - bgInfo.Left - cfg.BgScrollX - 1; + } else { + cfg.BgMinX = -1; + cfg.BgMaxX = -1; + } + } + } +} + +int32_t HdNesPack::GetLayerIndex(uint8_t priority) +{ + for(size_t i = 0; i < _hdData->Backgrounds.size(); i++) { + if(_hdData->Backgrounds[i].Priority != priority) { + continue; + } + + bool isMatch = true; + for(HdPackCondition* condition : _hdData->Backgrounds[i].Conditions) { + if(!condition->CheckCondition(_hdScreenInfo, 0, 0, nullptr)) { + isMatch = false; + break; + } + } + + if(isMatch) { + return (int32_t)i; + } + } + return -1; +} + +uint32_t palette[512] = {}; + +void HdNesPack::OnBeforeApplyFilter() +{ + _palette = palette; + //TODO + //_palette = _hdData->Palette.size() == 0x40 ? _hdData->Palette.data() : _settings->GetRgbPalette(); + _cacheEnabled = (_hdData->OptionFlags & (int)HdPackOptions::DisableCache) == 0; + //TODO + /* + if(_hdData->OptionFlags & (int)HdPackOptions::NoSpriteLimit) { + _settings->SetFlags(EmulationFlags::RemoveSpriteLimit | EmulationFlags::AdaptiveSpriteLimit); + }*/ + + for(int layer = 0; layer < 4; layer++) { + uint32_t activeCount = 0; + for(int i = 0; i < HdNesPack::PriorityLevelsPerLayer; i++) { + int32_t index = GetLayerIndex(layer * HdNesPack::PriorityLevelsPerLayer + i); + if(index >= 0) { + _bgConfig[layer*10+activeCount].BackgroundIndex = index; + activeCount++; + } + } + _activeBgCount[layer] = activeCount; + } + + for(unique_ptr &condition : _hdData->Conditions) { + condition->ClearCache(); + } +} + +HdPackTileInfo* HdNesPack::GetCachedMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile) +{ + if(((_scrollX + x) & 0x07) == 0) { + _useCachedTile = false; + } + + bool disableCache = false; + HdPackTileInfo* hdPackTileInfo; + if(_useCachedTile) { + hdPackTileInfo = _cachedTile; + } else { + hdPackTileInfo = GetMatchingTile(x, y, tile, &disableCache); + + if(!disableCache && _cacheEnabled) { + //Use this tile for the next 8 horizontal pixels + //Disable cache if a sprite condition is used, because sprites are not on a 8x8 grid + _cachedTile = hdPackTileInfo; + _useCachedTile = true; + } + } + return hdPackTileInfo; +} + +HdPackTileInfo* HdNesPack::GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile, bool* disableCache) +{ + auto hdTile = _hdData->TileByKey.find(*tile); + if(hdTile == _hdData->TileByKey.end()) { + hdTile = _hdData->TileByKey.find(tile->GetKey(true)); + } + + if(hdTile != _hdData->TileByKey.end()) { + for(HdPackTileInfo* hdPackTile : hdTile->second) { + if(disableCache != nullptr && hdPackTile->ForceDisableCache) { + *disableCache = true; + } + + if(hdPackTile->MatchesCondition(_hdScreenInfo, x, y, tile)) { + return hdPackTile; + } + } + } + + return nullptr; +} + +bool HdNesPack::DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth) +{ + HdBgConfig bgConfig = _bgConfig[(int)priority]; + if((int32_t)x >= bgConfig.BgMinX && (int32_t)x <= bgConfig.BgMaxX) { + HdBackgroundInfo& bgInfo = _hdData->Backgrounds[bgConfig.BackgroundIndex]; + DrawCustomBackground(bgInfo, outputBuffer, x + bgConfig.BgScrollX, y + bgConfig.BgScrollY, _hdData->Scale, screenWidth); + return true; + } + return false; +} + +void HdNesPack::GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth) +{ + HdPackTileInfo *hdPackTileInfo = nullptr; + HdPackTileInfo *hdPackSpriteInfo = nullptr; + + bool hasSprite = pixelInfo.SpriteCount > 0; + bool renderOriginalTiles = ((_hdData->OptionFlags & (int)HdPackOptions::DontRenderOriginalTiles) == 0); + if(pixelInfo.Tile.TileIndex != HdPpuTileInfo::NoTile) { + hdPackTileInfo = GetCachedMatchingTile(x, y, &pixelInfo.Tile); + } + + int lowestBgSprite = 999; + + DrawColor(_palette[pixelInfo.Tile.PpuBackgroundColor], outputBuffer, _hdData->Scale, screenWidth); + + bool hasBackground = false; + for(int i = 0; i < _activeBgCount[0]; i++) { + hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgSpritesPriority+i, x, y, outputBuffer, screenWidth); + } + + if(hasSprite) { + for(int k = pixelInfo.SpriteCount - 1; k >= 0; k--) { + if(pixelInfo.Sprite[k].BackgroundPriority) { + if(pixelInfo.Sprite[k].SpriteColorIndex != 0) { + lowestBgSprite = k; + } + + hdPackSpriteInfo = GetMatchingTile(x, y, &pixelInfo.Sprite[k]); + if(hdPackSpriteInfo) { + DrawTile(pixelInfo.Sprite[k], *hdPackSpriteInfo, outputBuffer, screenWidth); + } else if(pixelInfo.Sprite[k].SpriteColorIndex != 0) { + DrawColor(_palette[pixelInfo.Sprite[k].SpriteColor], outputBuffer, _hdData->Scale, screenWidth); + } + } + } + } + + for(int i = 0; i < _activeBgCount[1]; i++) { + hasBackground |= DrawBackgroundLayer(HdNesPack::BehindBgPriority+i, x, y, outputBuffer, screenWidth); + } + + if(hdPackTileInfo) { + DrawTile(pixelInfo.Tile, *hdPackTileInfo, outputBuffer, screenWidth); + } else if(renderOriginalTiles) { + //Draw regular SD background tile + if(!hasBackground || pixelInfo.Tile.BgColorIndex != 0) { + DrawColor(_palette[pixelInfo.Tile.BgColor], outputBuffer, _hdData->Scale, screenWidth); + } + } + + for(int i = 0; i < _activeBgCount[2]; i++) { + DrawBackgroundLayer(HdNesPack::BehindFgSpritesPriority+i, x, y, outputBuffer, screenWidth); + } + + if(hasSprite) { + for(int k = pixelInfo.SpriteCount - 1; k >= 0; k--) { + if(!pixelInfo.Sprite[k].BackgroundPriority && lowestBgSprite > k) { + hdPackSpriteInfo = GetMatchingTile(x, y, &pixelInfo.Sprite[k]); + if(hdPackSpriteInfo) { + DrawTile(pixelInfo.Sprite[k], *hdPackSpriteInfo, outputBuffer, screenWidth); + } else if(pixelInfo.Sprite[k].SpriteColorIndex != 0) { + DrawColor(_palette[pixelInfo.Sprite[k].SpriteColor], outputBuffer, _hdData->Scale, screenWidth); + } + } + } + } + + for(int i = 0; i < _activeBgCount[3]; i++) { + DrawBackgroundLayer(HdNesPack::ForegroundPriority+i, x, y, outputBuffer, screenWidth); + } +} + +void HdNesPack::Process(HdScreenInfo *hdScreenInfo, uint32_t* outputBuffer, OverscanDimensions &overscan) +{ + _hdScreenInfo = hdScreenInfo; + uint32_t hdScale = GetScale(); + uint32_t screenWidth = (NesConstants::ScreenWidth - overscan.Left - overscan.Right) * hdScale; + + OnBeforeApplyFilter(); + for(uint32_t i = overscan.Top, iMax = 240 - overscan.Bottom; i < iMax; i++) { + OnLineStart(hdScreenInfo->ScreenTiles[i << 8], i); + uint32_t bufferIndex = (i - overscan.Top) * screenWidth * hdScale; + uint32_t lineStartIndex = bufferIndex; + for(uint32_t j = overscan.Left, jMax = 256 - overscan.Right; j < jMax; j++) { + GetPixels(j, i, hdScreenInfo->ScreenTiles[i * 256 + j], outputBuffer + bufferIndex, screenWidth); + bufferIndex += hdScale; + } + + ProcessGrayscaleAndEmphasis(hdScreenInfo->ScreenTiles[i * 256], outputBuffer + lineStartIndex, screenWidth); + } +} + +void HdNesPack::ProcessGrayscaleAndEmphasis(HdPpuPixelInfo &pixelInfo, uint32_t* outputBuffer, uint32_t hdScreenWidth) +{ + //Apply grayscale/emphasis bits on a scanline level (less accurate, but shouldn't cause issues and simpler to implement) + uint32_t scale = GetScale(); + if(pixelInfo.Grayscale) { + uint32_t* out = outputBuffer; + for(uint32_t y = 0; y < scale; y++) { + for(uint32_t x = 0; x < hdScreenWidth; x++) { + uint32_t &rgbValue = out[x]; + uint8_t average = (((rgbValue >> 16) & 0xFF) + ((rgbValue >> 8) & 0xFF) + (rgbValue & 0xFF)) / 3; + rgbValue = (rgbValue & 0xFF000000) | (average << 16) | (average << 8) | average; + } + out += hdScreenWidth; + } + } + + if(pixelInfo.EmphasisBits) { + uint8_t emphasisBits = pixelInfo.EmphasisBits; + double red = 1.0, green = 1.0, blue = 1.0; + if(emphasisBits & 0x01) { + //Intensify red + red *= 1.1; + green *= 0.9; + blue *= 0.9; + } + if(emphasisBits & 0x02) { + //Intensify green + green *= 1.1; + red *= 0.9; + blue *= 0.9; + } + if(emphasisBits & 0x04) { + //Intensify blue + blue *= 1.1; + red *= 0.9; + green *= 0.9; + } + + uint32_t* out = outputBuffer; + for(uint32_t y = 0; y < scale; y++) { + for(uint32_t x = 0; x < hdScreenWidth; x++) { + uint32_t &rgbValue = out[x]; + + rgbValue = 0xFF000000 | + (std::min((uint16_t)(((rgbValue >> 16) & 0xFF) * red), 255) << 16) | + (std::min((uint16_t)(((rgbValue >> 8) & 0xFF) * green), 255) << 8) | + std::min((uint16_t)((rgbValue & 0xFF) * blue), 255); + } + out += hdScreenWidth; + } + } +} \ No newline at end of file diff --git a/Core/NES/HdPacks/HdNesPack.h b/Core/NES/HdPacks/HdNesPack.h new file mode 100644 index 00000000..6a894545 --- /dev/null +++ b/Core/NES/HdPacks/HdNesPack.h @@ -0,0 +1,61 @@ +#pragma once +#include "stdafx.h" +#include "NES/HdPacks/HdData.h" + +class HdNesPack +{ +private: + struct HdBgConfig + { + int32_t BackgroundIndex = -1; + int32_t BgScrollX = 0; + int32_t BgScrollY = 0; + int16_t BgMinX = -1; + int16_t BgMaxX = -1; + }; + + HdPackData* _hdData; + + static constexpr uint8_t PriorityLevelsPerLayer = 10; + static constexpr uint8_t BehindBgSpritesPriority = 0 * PriorityLevelsPerLayer; + static constexpr uint8_t BehindBgPriority = 1 * PriorityLevelsPerLayer; + static constexpr uint8_t BehindFgSpritesPriority = 2 * PriorityLevelsPerLayer; + static constexpr uint8_t ForegroundPriority = 3 * PriorityLevelsPerLayer; + + uint8_t _activeBgCount[4] = {}; + HdBgConfig _bgConfig[40] = {}; + + HdScreenInfo *_hdScreenInfo = nullptr; + uint32_t* _palette = nullptr; + HdPackTileInfo* _cachedTile = nullptr; + bool _cacheEnabled = false; + bool _useCachedTile = false; + int32_t _scrollX = 0; + + __forceinline void BlendColors(uint8_t output[4], uint8_t input[4]); + __forceinline uint32_t AdjustBrightness(uint8_t input[4], int brightness); + __forceinline void DrawColor(uint32_t color, uint32_t* outputBuffer, uint32_t scale, uint32_t screenWidth); + __forceinline void DrawTile(HdPpuTileInfo &tileInfo, HdPackTileInfo &hdPackTileInfo, uint32_t* outputBuffer, uint32_t screenWidth); + + __forceinline HdPackTileInfo* GetCachedMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile); + __forceinline HdPackTileInfo* GetMatchingTile(uint32_t x, uint32_t y, HdPpuTileInfo* tile, bool* disableCache = nullptr); + + __forceinline bool DrawBackgroundLayer(uint8_t priority, uint32_t x, uint32_t y, uint32_t* outputBuffer, uint32_t screenWidth); + __forceinline void DrawCustomBackground(HdBackgroundInfo& bgInfo, uint32_t *outputBuffer, uint32_t x, uint32_t y, uint32_t scale, uint32_t screenWidth); + + void OnLineStart(HdPpuPixelInfo &lineFirstPixel, uint8_t y); + int32_t GetLayerIndex(uint8_t priority); + void OnBeforeApplyFilter(); + __forceinline void GetPixels(uint32_t x, uint32_t y, HdPpuPixelInfo &pixelInfo, uint32_t *outputBuffer, uint32_t screenWidth); + __forceinline void ProcessGrayscaleAndEmphasis(HdPpuPixelInfo &pixelInfo, uint32_t* outputBuffer, uint32_t hdScreenWidth); + +public: + static constexpr uint32_t CurrentVersion = 106; + + HdNesPack(HdPackData* hdData); + ~HdNesPack(); + + uint32_t GetScale(); + + void Process(HdScreenInfo *hdScreenInfo, uint32_t *outputBuffer, OverscanDimensions &overscan); +}; diff --git a/Core/NES/HdPacks/HdNesPpu.cpp b/Core/NES/HdPacks/HdNesPpu.cpp new file mode 100644 index 00000000..8c36b4f4 --- /dev/null +++ b/Core/NES/HdPacks/HdNesPpu.cpp @@ -0,0 +1,51 @@ +#include "stdafx.h" +#include "NES/HdPacks/HdNesPpu.h" +#include "NES/NesConsole.h" +#include "NES/HdPacks/HdPackConditions.h" +#include "NES/NesMemoryManager.h" +#include "NES/BaseMapper.h" +#include "NES/HdPacks/HdData.h" + +HdNesPpu::HdNesPpu(NesConsole* console, HdPackData* hdData) : NesPpu(console) +{ + _hdData = hdData; + + if(_hdData) { + _version = _hdData->Version; + + bool isChrRamGame = !console->GetMapper()->HasChrRom(); + _screenInfo[0] = new HdScreenInfo(isChrRamGame); + _screenInfo[1] = new HdScreenInfo(isChrRamGame); + _info = _screenInfo[0]; + } +} + +HdNesPpu::~HdNesPpu() +{ + if(_hdData) { + delete _screenInfo[0]; + delete _screenInfo[1]; + } +} + +void* HdNesPpu::OnBeforeSendFrame() +{ + HdScreenInfo* info = _info; + info->FrameNumber = _frameCount; + info->WatchedAddressValues.clear(); + for(uint32_t address : _hdData->WatchedMemoryAddresses) { + if(address & HdPackBaseMemoryCondition::PpuMemoryMarker) { + if((address & 0x3FFF) >= 0x3F00) { + info->WatchedAddressValues[address] = ReadPaletteRAM(address); + } else { + info->WatchedAddressValues[address] = _console->GetMapper()->DebugReadVram(address & 0x3FFF, true); + } + } else { + info->WatchedAddressValues[address] = _console->GetMemoryManager()->DebugRead(address); + } + } + + _info = (_info == _screenInfo[0]) ? _screenInfo[1] : _screenInfo[0]; + + return info; +} diff --git a/Core/NES/HdPacks/HdNesPpu.h b/Core/NES/HdPacks/HdNesPpu.h index 1c0727cf..b0a24ed7 100644 --- a/Core/NES/HdPacks/HdNesPpu.h +++ b/Core/NES/HdPacks/HdNesPpu.h @@ -1,22 +1,62 @@ #pragma once #include "stdafx.h" #include "NES/NesPpu.h" +#include "NES/NesConsole.h" +#include "NES/HdPacks/HdPackConditions.h" +#include "NES/NesMemoryManager.h" +#include "NES/BaseMapper.h" +#include "NES/HdPacks/HdData.h" class HdNesPpu final : public NesPpu { + struct NesSpriteInfoEx + { + uint32_t AbsoluteTileAddr; + uint16_t TileAddr; + bool VerticalMirror; + uint8_t OffsetY; + }; + + struct NesTileInfoEx + { + uint32_t AbsoluteTileAddr; + uint8_t OffsetY; + }; + + HdScreenInfo* _screenInfo[2] = {}; + HdScreenInfo* _info = nullptr; + uint32_t _version = 0; + HdPackData* _hdData = nullptr; + NesSpriteInfoEx _exSpriteInfo[64] = {}; + NesTileInfoEx _previousTileEx = {}; + NesTileInfoEx _currentTileEx = {}; + NesTileInfoEx _nextTileEx = {}; + public: - HdNesPpu(NesConsole* console) : NesPpu(console) + HdNesPpu(NesConsole* console, HdPackData* hdData); + virtual ~HdNesPpu(); + + void* OnBeforeSendFrame(); + + __forceinline void StoreSpriteInformation(bool verticalMirror, uint16_t tileAddr, uint8_t lineOffset) { + NesSpriteInfoEx& info = _exSpriteInfo[_spriteIndex]; + info.TileAddr = tileAddr; + info.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(info.TileAddr).Address; + info.VerticalMirror = verticalMirror; + info.OffsetY = lineOffset; } - __forceinline void StoreSpriteAbsoluteAddress() + __forceinline void StoreTileInformation() { - _spriteTiles[_spriteIndex].AbsoluteTileAddr = _mapper->GetAbsoluteAddress(_spriteTiles[_spriteIndex].TileAddr).Address; - } + _previousTileEx = _currentTileEx; + _currentTileEx = _nextTileEx; - __forceinline void StoreTileAbsoluteAddress() - { - _tile.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(_tile.TileAddr).Address; + uint8_t tileIndex = ReadVram(GetNameTableAddr()); + uint16_t tileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _backgroundPatternAddr; + + _nextTileEx.OffsetY = _videoRamAddr >> 12; + _nextTileEx.AbsoluteTileAddr = _mapper->GetPpuAbsoluteAddress(tileAddr).Address; } __forceinline void ProcessScanline() @@ -24,15 +64,115 @@ public: ProcessScanlineImpl(); } - __forceinline void DrawPixel() + void DrawPixel() { - //This is called 3.7 million times per second - needs to be as fast as possible. - if(IsRenderingEnabled() || ((_state.VideoRamAddr & 0x3F00) != 0x3F00)) { + uint16_t bufferOffset = (_scanline << 8) + _cycle - 1; + uint16_t& pixel = _currentOutputBuffer[bufferOffset]; + _lastSprite = nullptr; + + if(IsRenderingEnabled() || ((_videoRamAddr & 0x3F00) != 0x3F00)) { + bool isChrRam = !_console->GetMapper()->HasChrRom(); + BaseMapper* mapper = _console->GetMapper(); + uint32_t color = GetPixelColor(); - _currentOutputBuffer[(_scanline << 8) + _cycle - 1] = _paletteRAM[color & 0x03 ? color : 0]; + pixel = (_paletteRAM[color & 0x03 ? color : 0] & _paletteRamMask) | _intensifyColorBits; + + uint8_t tilePalette = (_xScroll + ((_cycle - 1) & 0x07) < 8) ? _previousTilePalette : _currentTilePalette; + NesTileInfoEx& lastTileEx = (_xScroll + ((_cycle - 1) & 0x07) < 8) ? _previousTileEx : _currentTileEx; + uint32_t backgroundColor = 0; + if(_backgroundEnabled && _cycle > _minimumDrawBgCycle) { + backgroundColor = (((_lowBitShift << _xScroll) & 0x8000) >> 15) | (((_highBitShift << _xScroll) & 0x8000) >> 14); + } + + HdPpuPixelInfo& tileInfo = _info->ScreenTiles[bufferOffset]; + + tileInfo.Grayscale = _paletteRamMask == 0x30; + tileInfo.EmphasisBits = _intensifyColorBits >> 6; + tileInfo.Tile.PpuBackgroundColor = ReadPaletteRAM(0); + tileInfo.Tile.BgColorIndex = backgroundColor; + if(backgroundColor == 0) { + tileInfo.Tile.BgColor = tileInfo.Tile.PpuBackgroundColor; + } else { + tileInfo.Tile.BgColor = ReadPaletteRAM(tilePalette + backgroundColor); + } + + tileInfo.XScroll = _xScroll; + tileInfo.TmpVideoRamAddr = _tmpVideoRamAddr; + + if(_lastSprite && _spritesEnabled) { + int j = 0; + for(uint8_t i = 0; i < _spriteCount; i++) { + int32_t shift = (int32_t)_cycle - _spriteTiles[i].SpriteX - 1; + NesSpriteInfo& sprite = _spriteTiles[i]; + NesSpriteInfoEx& spriteEx = _exSpriteInfo[i]; + if(shift >= 0 && shift < 8) { + tileInfo.Sprite[j].TileIndex = spriteEx.AbsoluteTileAddr / 16; + if(isChrRam) { + mapper->CopyChrTile(spriteEx.AbsoluteTileAddr & 0xFFFFFFF0, tileInfo.Sprite[j].TileData); + } + if(_version >= 100) { + tileInfo.Sprite[j].PaletteColors = 0xFF000000 | _paletteRAM[sprite.PaletteOffset + 3] | (_paletteRAM[sprite.PaletteOffset + 2] << 8) | (_paletteRAM[sprite.PaletteOffset + 1] << 16); + } else { + tileInfo.Sprite[j].PaletteColors = _paletteRAM[sprite.PaletteOffset + 3] | (_paletteRAM[sprite.PaletteOffset + 2] << 8) | (_paletteRAM[sprite.PaletteOffset + 1] << 16); + } + if(spriteEx.OffsetY >= 8) { + tileInfo.Sprite[j].OffsetY = spriteEx.OffsetY - 8; + } else { + tileInfo.Sprite[j].OffsetY = spriteEx.OffsetY; + } + + tileInfo.Sprite[j].OffsetX = shift; + tileInfo.Sprite[j].HorizontalMirroring = sprite.HorizontalMirror; + tileInfo.Sprite[j].VerticalMirroring = spriteEx.VerticalMirror; + tileInfo.Sprite[j].BackgroundPriority = sprite.BackgroundPriority; + + int32_t shift = (int32_t)_cycle - sprite.SpriteX - 1; + if(sprite.HorizontalMirror) { + tileInfo.Sprite[j].SpriteColorIndex = ((sprite.LowByte >> shift) & 0x01) | ((sprite.HighByte >> shift) & 0x01) << 1; + } else { + tileInfo.Sprite[j].SpriteColorIndex = ((sprite.LowByte << shift) & 0x80) >> 7 | ((sprite.HighByte << shift) & 0x80) >> 6; + } + + if(tileInfo.Sprite[j].SpriteColorIndex == 0) { + tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(0); + } else { + tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(sprite.PaletteOffset + tileInfo.Sprite[j].SpriteColorIndex); + } + + tileInfo.Sprite[j].PpuBackgroundColor = tileInfo.Tile.PpuBackgroundColor; + tileInfo.Sprite[j].BgColorIndex = tileInfo.Tile.BgColorIndex; + + j++; + if(j >= 4) { + break; + } + } + } + tileInfo.SpriteCount = j; + } else { + tileInfo.SpriteCount = 0; + } + + if(_backgroundEnabled && _cycle > _minimumDrawBgCycle) { + tileInfo.Tile.TileIndex = lastTileEx.AbsoluteTileAddr / 16; + if(isChrRam) { + mapper->CopyChrTile(lastTileEx.AbsoluteTileAddr & 0xFFFFFFF0, tileInfo.Tile.TileData); + } + if(_version >= 100) { + tileInfo.Tile.PaletteColors = _paletteRAM[tilePalette + 3] | (_paletteRAM[tilePalette + 2] << 8) | (_paletteRAM[tilePalette + 1] << 16) | (_paletteRAM[0] << 24); + } else { + tileInfo.Tile.PaletteColors = _paletteRAM[tilePalette + 3] | (_paletteRAM[tilePalette + 2] << 8) | (_paletteRAM[tilePalette + 1] << 16); + } + tileInfo.Tile.OffsetY = lastTileEx.OffsetY; + tileInfo.Tile.OffsetX = (_xScroll + ((_cycle - 1) & 0x07)) & 0x07; + } else { + tileInfo.Tile.TileIndex = HdPpuTileInfo::NoTile; + } } else { //"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color." - _currentOutputBuffer[(_scanline << 8) + _cycle - 1] = _paletteRAM[_state.VideoRamAddr & 0x1F]; + pixel = ReadPaletteRAM(_videoRamAddr) | _intensifyColorBits; + _info->ScreenTiles[bufferOffset].Tile.TileIndex = HdPpuTileInfo::NoTile; + _info->ScreenTiles[bufferOffset].SpriteCount = 0; } } }; \ No newline at end of file diff --git a/Core/NES/HdPacks/HdPackConditions.h b/Core/NES/HdPacks/HdPackConditions.h new file mode 100644 index 00000000..e7dcee76 --- /dev/null +++ b/Core/NES/HdPacks/HdPackConditions.h @@ -0,0 +1,301 @@ +#pragma once +#include "stdafx.h" +#include "NES/HdPacks/HdData.h" +#include "NES/NesConstants.h" +#include "Utilities/HexUtilities.h" + +struct HdPackBaseTileCondition : public HdPackCondition +{ + int32_t TileX; + int32_t TileY; + uint32_t PaletteColors; + uint8_t TileData[16]; + int32_t TileIndex; + int32_t PixelOffset; + + void Initialize(int32_t x, int32_t y, uint32_t palette, int32_t tileIndex, string tileData = "") + { + TileX = x; + TileY = y; + PixelOffset = (y * 256) + x; + PaletteColors = palette; + TileIndex = tileIndex; + if(tileData.size() == 32) { + for(int i = 0; i < 16; i++) { + TileData[i] = HexUtilities::FromHex(tileData.substr(i * 2, 2)); + } + TileIndex = -1; + } + } + + string ToString() override + { + stringstream out; + out << "" << Name << "," << GetConditionName() << ","; + out << TileX << ","; + out << TileY << ","; + if(TileIndex >= 0) { + out << HexUtilities::ToHex(TileIndex) << ","; + } else { + for(int i = 0; i < 16; i++) { + out << HexUtilities::ToHex(TileData[i]); + } + } + out << HexUtilities::ToHex(PaletteColors, true); + + return out.str(); + } +}; + +enum class HdPackConditionOperator +{ + Equal = 0, + NotEqual = 1, + GreaterThan = 2, + LowerThan = 3, + LowerThanOrEqual = 4, + GreaterThanOrEqual = 5, +}; + +struct HdPackBaseMemoryCondition : public HdPackCondition +{ + static constexpr uint32_t PpuMemoryMarker = 0x80000000; + uint32_t OperandA; + HdPackConditionOperator Operator; + uint32_t OperandB; + uint8_t Mask; + + void Initialize(uint32_t operandA, HdPackConditionOperator op, uint32_t operandB, uint8_t mask) + { + OperandA = operandA; + Operator = op; + OperandB = operandB; + Mask = mask; + } + + bool IsPpuCondition() + { + return (OperandA & HdPackBaseMemoryCondition::PpuMemoryMarker) != 0; + } + + string ToString() override + { + stringstream out; + out << "" << Name << "," << GetConditionName() << ","; + out << HexUtilities::ToHex(OperandA & 0xFFFF) << ","; + switch(Operator) { + case HdPackConditionOperator::Equal: out << "=="; break; + case HdPackConditionOperator::NotEqual: out << "!="; break; + case HdPackConditionOperator::GreaterThan: out << ">"; break; + case HdPackConditionOperator::LowerThan: out << "<"; break; + case HdPackConditionOperator::LowerThanOrEqual: out << "<="; break; + case HdPackConditionOperator::GreaterThanOrEqual: out << ">="; break; + } + out << ","; + out << HexUtilities::ToHex(OperandB); + out << ","; + out << HexUtilities::ToHex(Mask); + + return out.str(); + } +}; + +struct HdPackHorizontalMirroringCondition : public HdPackCondition +{ + string GetConditionName() override { return "hmirror"; } + string ToString() override { return ""; } + bool IsExcludedFromFile() override { return true; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + return tile && tile->HorizontalMirroring; + } +}; + +struct HdPackVerticalMirroringCondition : public HdPackCondition +{ + string GetConditionName() override { return "vmirror"; } + string ToString() override { return ""; } + bool IsExcludedFromFile() override { return true; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + return tile && tile->VerticalMirroring; + } +}; + +struct HdPackBgPriorityCondition : public HdPackCondition +{ + string GetConditionName() override { return "bgpriority"; } + string ToString() override { return ""; } + bool IsExcludedFromFile() override { return true; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + return tile && tile->BackgroundPriority; + } +}; + +struct HdPackMemoryCheckCondition : public HdPackBaseMemoryCondition +{ + HdPackMemoryCheckCondition() { _useCache = true; } + string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheck" : "memoryCheck"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + uint8_t a = (uint8_t)(screenInfo->WatchedAddressValues[OperandA] & Mask); + uint8_t b = (uint8_t)(screenInfo->WatchedAddressValues[OperandB] & Mask); + + switch(Operator) { + case HdPackConditionOperator::Equal: return a == b; + case HdPackConditionOperator::NotEqual: return a != b; + case HdPackConditionOperator::GreaterThan: return a > b; + case HdPackConditionOperator::LowerThan: return a < b; + case HdPackConditionOperator::LowerThanOrEqual: return a <= b; + case HdPackConditionOperator::GreaterThanOrEqual: return a >= b; + } + return false; + } +}; + +struct HdPackMemoryCheckConstantCondition : public HdPackBaseMemoryCondition +{ + HdPackMemoryCheckConstantCondition() { _useCache = true; } + string GetConditionName() override { return IsPpuCondition() ? "ppuMemoryCheckConstant" : "memoryCheckConstant"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + uint8_t a = (uint8_t)(screenInfo->WatchedAddressValues[OperandA] & Mask); + uint8_t b = OperandB; + + switch(Operator) { + case HdPackConditionOperator::Equal: return a == b; + case HdPackConditionOperator::NotEqual: return a != b; + case HdPackConditionOperator::GreaterThan: return a > b; + case HdPackConditionOperator::LowerThan: return a < b; + case HdPackConditionOperator::LowerThanOrEqual: return a <= b; + case HdPackConditionOperator::GreaterThanOrEqual: return a >= b; + } + return false; + } +}; + +struct HdPackFrameRangeCondition : public HdPackCondition +{ + uint32_t OperandA; + uint32_t OperandB; + + HdPackFrameRangeCondition() { _useCache = true; } + string GetConditionName() override { return "frameRange"; } + + void Initialize(uint32_t operandA, uint32_t operandB) + { + OperandA = operandA; + OperandB = operandB; + } + + string ToString() override + { + stringstream out; + out << "" << Name << "," << GetConditionName() << ","; + out << OperandA << ","; + out << OperandB; + + return out.str(); + } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + return screenInfo->FrameNumber % OperandA >= OperandB; + } +}; + +struct HdPackTileAtPositionCondition : public HdPackBaseTileCondition +{ + HdPackTileAtPositionCondition() { _useCache = true; } + string GetConditionName() override { return "tileAtPosition"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[PixelOffset].Tile; + if(TileIndex >= 0) { + return targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex; + } else { + return memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0; + } + } +}; + +struct HdPackSpriteAtPositionCondition : public HdPackBaseTileCondition +{ + HdPackSpriteAtPositionCondition() { _useCache = true; } + string GetConditionName() override { return "spriteAtPosition"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + for(int i = 0, len = screenInfo->ScreenTiles[PixelOffset].SpriteCount; i < len; i++) { + HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[PixelOffset].Sprite[i]; + if(TileIndex >= 0) { + if(targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex) { + return true; + } + } else { + if(memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0) { + return true; + } + } + } + return false; + } +}; + +struct HdPackTileNearbyCondition : public HdPackBaseTileCondition +{ + string GetConditionName() override { return "tileNearby"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + int pixelIndex = PixelOffset + (y * 256) + x; + if(pixelIndex < 0 || pixelIndex > NesConstants::ScreenPixelCount) { + return false; + } + + HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[pixelIndex].Tile; + if(TileIndex >= 0) { + return targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex; + } else { + return memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0; + } + } +}; + +struct HdPackSpriteNearbyCondition : public HdPackBaseTileCondition +{ + string GetConditionName() override { return "spriteNearby"; } + + bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) override + { + int xSign = tile && tile->HorizontalMirroring ? -1 : 1; + int ySign = tile && tile->VerticalMirroring ? -1 : 1; + int pixelIndex = ((y + TileY * ySign) * 256) + x + (TileX * xSign); + + if(pixelIndex < 0 || pixelIndex > NesConstants::ScreenPixelCount) { + return false; + } + + for(int i = 0, len = screenInfo->ScreenTiles[pixelIndex].SpriteCount; i < len; i++) { + HdPpuTileInfo &targetTile = screenInfo->ScreenTiles[pixelIndex].Sprite[i]; + if(TileIndex >= 0) { + if(targetTile.PaletteColors == PaletteColors && targetTile.TileIndex == TileIndex) { + return true; + } + } else { + if(memcmp(&targetTile.PaletteColors, &PaletteColors, sizeof(PaletteColors) + sizeof(TileData)) == 0) { + return true; + } + } + } + + return false; + } +}; diff --git a/Core/NES/HdPacks/HdPackLoader.cpp b/Core/NES/HdPacks/HdPackLoader.cpp new file mode 100644 index 00000000..0090dbc5 --- /dev/null +++ b/Core/NES/HdPacks/HdPackLoader.cpp @@ -0,0 +1,745 @@ +#include "stdafx.h" +#include +#include +#include "NES/HdPacks/HdPackLoader.h" +#include "NES/HdPacks/HdPackConditions.h" +#include "NES/HdPacks/HdNesPack.h" +#include "NES/NesConsole.h" +#include "Shared/MessageManager.h" +#include "Utilities/ZipReader.h" +#include "Utilities/FolderUtilities.h" +#include "Utilities/StringUtilities.h" +#include "Utilities/HexUtilities.h" +#include "Utilities/PNGHelper.h" + +#define checkConstraint(x, y) if(!(x)) { MessageManager::Log(y); return; } + +HdPackLoader::HdPackLoader() +{ +} + +bool HdPackLoader::InitializeLoader(VirtualFile &romFile, HdPackData *data) +{ + _data = data; + + string romName = FolderUtilities::GetFilename(romFile.GetFileName(), false); + string hdPackFolder = FolderUtilities::GetHdPackFolder(); + string zipName = romName + ".hdn"; + string definitionPath = FolderUtilities::CombinePath(romName, "hires.txt"); + + string legacyPath = FolderUtilities::CombinePath(hdPackFolder, definitionPath); + if(ifstream(legacyPath)) { + _loadFromZip = false; + _hdPackFolder = FolderUtilities::GetFolderName(legacyPath); + return true; + } else { + vector hdnPackages = FolderUtilities::GetFilesInFolder(romFile.GetFolderPath(), { ".hdn" }, false); + vector more = FolderUtilities::GetFilesInFolder(hdPackFolder, { ".hdn", ".zip" }, false); + hdnPackages.insert(hdnPackages.end(), more.begin(), more.end()); + + string sha1Hash = romFile.GetSha1Hash(); + for(string path : hdnPackages) { + _reader.LoadArchive(path); + + vector hdDefinition; + if(_reader.ExtractFile("hires.txt", hdDefinition)) { + if(FolderUtilities::GetFilename(path, false) == romName) { + _loadFromZip = true; + _hdPackFolder = path; + return true; + } else { + for(string line : StringUtilities::Split(string(hdDefinition.data(), hdDefinition.data() + hdDefinition.size()), '\n')) { + std::transform(line.begin(), line.end(), line.begin(), ::tolower); + if(line.find("") != string::npos && line.find(sha1Hash) != string::npos) { + _loadFromZip = true; + _hdPackFolder = path; + return true; + } + } + } + } + } + } + return false; +} + +bool HdPackLoader::LoadHdNesPack(string definitionFile, HdPackData &outData) +{ + HdPackLoader loader; + if(ifstream(definitionFile)) { + loader._data = &outData; + loader._loadFromZip = false; + loader._hdPackFolder = FolderUtilities::GetFolderName(definitionFile); + return loader.LoadPack(); + } + return false; +} + +bool HdPackLoader::LoadHdNesPack(VirtualFile &romFile, HdPackData &outData) +{ + HdPackLoader loader; + if(loader.InitializeLoader(romFile, &outData)) { + return loader.LoadPack(); + } + return false; +} + +bool HdPackLoader::CheckFile(string filename) +{ + if(_loadFromZip) { + return _reader.CheckFile(filename); + } else { + ifstream file(FolderUtilities::CombinePath(_hdPackFolder, filename), ios::in | ios::binary); + if(file.good()) { + return true; + } + } + + return false; +} + +bool HdPackLoader::LoadFile(string filename, vector &fileData) +{ + fileData.clear(); + + if(_loadFromZip) { + if(_reader.ExtractFile(filename, fileData)) { + return true; + } + } else { + ifstream file(FolderUtilities::CombinePath(_hdPackFolder, filename), ios::in | ios::binary); + if(file.good()) { + file.seekg(0, ios::end); + uint32_t fileSize = (uint32_t)file.tellg(); + file.seekg(0, ios::beg); + + fileData = vector(fileSize, 0); + file.read((char*)fileData.data(), fileSize); + + return true; + } + } + + return false; +} + +bool HdPackLoader::LoadPack() +{ + string currentLine; + try { + vector hdDefinition; + if(!LoadFile("hires.txt", hdDefinition)) { + return false; + } + + InitializeGlobalConditions(); + + for(string lineContent : StringUtilities::Split(string(hdDefinition.data(), hdDefinition.data() + hdDefinition.size()), '\n')) { + if(lineContent.empty()) { + continue; + } + + if(lineContent[lineContent.size() - 1] == '\r') { + lineContent = lineContent.substr(0, lineContent.size() - 1); + } + currentLine = lineContent; + + vector conditions; + if(lineContent.substr(0, 1) == "[") { + size_t endOfCondition = lineContent.find_first_of(']', 1); + conditions = ParseConditionString(lineContent.substr(1, endOfCondition - 1)); + lineContent = lineContent.substr(endOfCondition + 1); + } + + vector tokens; + if(lineContent.substr(0, 5) == "") { + _data->Version = stoi(lineContent.substr(5)); + if(_data->Version > HdNesPack::CurrentVersion) { + MessageManager::Log("[HDPack] This HD Pack was built with a more recent version of Mesen - update Mesen to the latest version and try again."); + return false; + } + } else if(lineContent.substr(0, 7) == "") { + lineContent = lineContent.substr(7); + _data->Scale = std::stoi(lineContent); + } else if(lineContent.substr(0, 10) == "") { + tokens = StringUtilities::Split(lineContent.substr(10), ','); + ProcessOverscanTag(tokens); + } else if(lineContent.substr(0, 5) == "") { + lineContent = lineContent.substr(5); + if(!ProcessImgTag(lineContent)) { + return false; + } + } else if(lineContent.substr(0, 7) == "") { + tokens = StringUtilities::Split(lineContent.substr(7), ','); + ProcessPatchTag(tokens); + } else if(lineContent.substr(0, 12) == "") { + tokens = StringUtilities::Split(lineContent.substr(12), ','); + ProcessBackgroundTag(tokens, conditions); + } else if(lineContent.substr(0, 11) == "") { + tokens = StringUtilities::Split(lineContent.substr(11), ','); + ProcessConditionTag(tokens, false); + ProcessConditionTag(tokens, true); + } else if(lineContent.substr(0, 6) == "") { + tokens = StringUtilities::Split(lineContent.substr(6), ','); + ProcessTileTag(tokens, conditions); + } else if(lineContent.substr(0, 9) == "") { + tokens = StringUtilities::Split(lineContent.substr(9), ','); + ProcessOptionTag(tokens); + } else if(lineContent.substr(0, 5) == "") { + tokens = StringUtilities::Split(lineContent.substr(5), ','); + ProcessBgmTag(tokens); + } else if(lineContent.substr(0, 5) == "") { + tokens = StringUtilities::Split(lineContent.substr(5), ','); + ProcessSfxTag(tokens); + } + } + + LoadCustomPalette(); + InitializeHdPack(); + + return true; + } catch(std::exception &ex) { + MessageManager::Log(string("[HDPack] Error loading HDPack: ") + ex.what() + " on line: " + currentLine); + return false; + } +} + +bool HdPackLoader::ProcessImgTag(string src) +{ + vector fileData; + vector pixelData; + LoadFile(src, fileData); + + _hdNesBitmaps.push_back({}); + HdPackBitmapInfo& bitmapInfo = _hdNesBitmaps.back(); + if(PNGHelper::ReadPNG(fileData, bitmapInfo.PixelData, bitmapInfo.Width, bitmapInfo.Height)) { + return true; + } else { + _hdNesBitmaps.pop_back(); + MessageManager::Log("[HDPack] Error loading HDPack: PNG file " + src + " could not be read."); + return false; + } +} + +void HdPackLoader::PremultiplyAlpha(vector &pixelData) +{ + for(size_t i = 0; i < pixelData.size(); i++) { + if(pixelData[i] < 0xFF000000) { + //If not fully opaque, pre-multiply alpha with R/G/B to avoid having to do this while running + uint8_t* output = (uint8_t*)(pixelData.data() + i); + uint8_t alpha = output[3] + 1; + output[0] = (uint8_t)((alpha * output[0]) >> 8); + output[1] = (uint8_t)((alpha * output[1]) >> 8); + output[2] = (uint8_t)((alpha * output[2]) >> 8); + } + } +} + +void HdPackLoader::InitializeGlobalConditions() +{ + HdPackCondition* hmirror = new HdPackHorizontalMirroringCondition(); + hmirror->Name = "hmirror"; + _data->Conditions.push_back(unique_ptr(hmirror)); + + HdPackCondition* invHmirror = new HdPackHorizontalMirroringCondition(); + invHmirror->Name = "!hmirror"; + _data->Conditions.push_back(unique_ptr(invHmirror)); + + HdPackCondition* vmirror = new HdPackVerticalMirroringCondition(); + vmirror->Name = "vmirror"; + _data->Conditions.push_back(unique_ptr(vmirror)); + + HdPackCondition* invVmirror = new HdPackVerticalMirroringCondition(); + invVmirror->Name = "!vmirror"; + _data->Conditions.push_back(unique_ptr(invVmirror)); + + HdPackCondition* bgpriority = new HdPackBgPriorityCondition(); + bgpriority->Name = "bgpriority"; + _data->Conditions.push_back(unique_ptr(bgpriority)); + + HdPackCondition* invBgpriority = new HdPackBgPriorityCondition(); + invBgpriority->Name = "!bgpriority"; + _data->Conditions.push_back(unique_ptr(invBgpriority)); +} + +void HdPackLoader::ProcessOverscanTag(vector &tokens) +{ + OverscanDimensions overscan; + overscan.Top = std::stoi(tokens[0]); + overscan.Right = std::stoi(tokens[1]); + overscan.Bottom = std::stoi(tokens[2]); + overscan.Left = std::stoi(tokens[3]); + _data->HasOverscanConfig = true; + _data->Overscan = overscan; +} + +void HdPackLoader::ProcessPatchTag(vector &tokens) +{ + checkConstraint(tokens.size() >= 2, "[HDPack] Patch tag requires more parameters"); + checkConstraint(tokens[1].size() == 40, string("[HDPack] Invalid SHA1 hash for patch (" + tokens[0] + "): " + tokens[1])); + + vector fileData; + if(!LoadFile(tokens[0], fileData)) { + MessageManager::Log(string("[HDPack] Patch file not found: " + tokens[1])); + return; + } + + std::transform(tokens[1].begin(), tokens[1].end(), tokens[1].begin(), ::toupper); + if(_loadFromZip) { + _data->PatchesByHash[tokens[1]] = VirtualFile(_hdPackFolder, tokens[0]); + } else { + _data->PatchesByHash[tokens[1]] = FolderUtilities::CombinePath(_hdPackFolder, tokens[0]); + } +} + +void HdPackLoader::ProcessTileTag(vector &tokens, vector conditions) +{ + HdPackTileInfo *tileInfo = new HdPackTileInfo(); + size_t index = 0; + if(_data->Version < 100) { + tileInfo->TileIndex = std::stoi(tokens[index++]); + tileInfo->BitmapIndex = std::stoi(tokens[index++]); + tileInfo->PaletteColors = std::stoi(tokens[index + 2]) | (std::stoi(tokens[index + 1]) << 8) | (std::stoi(tokens[index]) << 16); + index += 3; + } else { + tileInfo->BitmapIndex = std::stoi(tokens[index++]); + string tileData = tokens[index++]; + if(tileData.size() >= 32) { + //CHR RAM tile, read the tile data + for(int i = 0; i < 16; i++) { + tileInfo->TileData[i] = HexUtilities::FromHex(tileData.substr(i * 2, 2)); + } + tileInfo->IsChrRamTile = true; + tileInfo->TileIndex = -1; + } else { + if(_data->Version <= 102) { + tileInfo->TileIndex = std::stoi(tileData); + } else { + tileInfo->TileIndex = HexUtilities::FromHex(tileData); + } + tileInfo->IsChrRamTile = false; + } + tileInfo->PaletteColors = HexUtilities::FromHex(tokens[index++]); + } + tileInfo->X = std::stoi(tokens[index++]); + tileInfo->Y = std::stoi(tokens[index++]); + tileInfo->Conditions = conditions; + tileInfo->ForceDisableCache = false; + for(HdPackCondition* condition : conditions) { + if(dynamic_cast(condition)) { + tileInfo->ForceDisableCache = true; + break; + } else if(dynamic_cast(condition)) { + HdPackTileNearbyCondition* tileNearby = dynamic_cast(condition); + if(tileNearby->TileX % 8 > 0 || tileNearby->TileY % 8 > 0) { + tileInfo->ForceDisableCache = true; + break; + } + } + } + + if(_data->Version >= 105) { + tileInfo->Brightness = (int)(std::stof(tokens[index++]) * 255); + } else if(_data->Version > 0) { + tileInfo->Brightness = (uint8_t)(std::stof(tokens[index++]) * 255); + } else { + tileInfo->Brightness = 255; + } + tileInfo->DefaultTile = (tokens[index++] == "Y"); + + //For CHR ROM tiles, the ID is just the bank number in chr rom (4k banks) + tileInfo->ChrBankId = tileInfo->TileIndex / 256; + + if(_data->Version < 100) { + if(tokens.size() >= 24) { + //CHR RAM tile, read the tile data + for(int i = 0; i < 16; i++) { + tileInfo->TileData[i] = std::stoi(tokens[index++]); + } + tileInfo->IsChrRamTile = true; + } else { + tileInfo->IsChrRamTile = false; + } + } else { + if(tileInfo->IsChrRamTile && tokens.size() > index) { + tileInfo->ChrBankId = std::stoul(tokens[index++]); + } + if(tileInfo->IsChrRamTile && tokens.size() > index) { + tileInfo->TileIndex = std::stoi(tokens[index++]); + } + } + + checkConstraint(tileInfo->BitmapIndex < _hdNesBitmaps.size(), "[HDPack] Invalid bitmap index: " + std::to_string(tileInfo->BitmapIndex)); + + HdPackBitmapInfo &bitmapInfo = _hdNesBitmaps[tileInfo->BitmapIndex]; + + uint32_t bitmapOffset = (tileInfo->Y * bitmapInfo.Width + tileInfo->X) * sizeof(uint32_t); + uint8_t* pngData = bitmapInfo.PixelData.data(); + + tileInfo->HdTileData.resize(64 * _data->Scale * _data->Scale); + for(uint32_t y = 0; y < 8 * _data->Scale; y++) { + for(uint32_t x = 0; x < 8 * _data->Scale; x++) { + uint8_t r = pngData[bitmapOffset]; + uint8_t g = pngData[bitmapOffset + 1]; + uint8_t b = pngData[bitmapOffset + 2]; + uint8_t a = pngData[bitmapOffset + 3]; + + if(a < 0xFF) { + //If not fully opaque, pre-multiply alpha with R/G/B to avoid having to do this while running + uint8_t alpha = a + 1; + r = (uint8_t)((alpha * r) >> 8); + g = (uint8_t)((alpha * g) >> 8); + b = (uint8_t)((alpha * b) >> 8); + } + + tileInfo->HdTileData[y * 8 * _data->Scale + x] = (a << 24) | (r << 16) | (g << 8) | b; + bitmapOffset += sizeof(uint32_t); + } + bitmapOffset += (bitmapInfo.Width - (8 * _data->Scale)) * sizeof(uint32_t); + } + + tileInfo->UpdateFlags(); + + _data->Tiles.push_back(unique_ptr(tileInfo)); +} + +void HdPackLoader::ProcessOptionTag(vector &tokens) +{ + for(string token : tokens) { + if(token == "disableSpriteLimit") { + _data->OptionFlags |= (int)HdPackOptions::NoSpriteLimit; + } else if(token == "alternateRegisterRange") { + _data->OptionFlags |= (int)HdPackOptions::AlternateRegisterRange; + } else if(token == "disableCache") { + _data->OptionFlags |= (int)HdPackOptions::DisableCache; + } else if(token == "disableOriginalTiles") { + _data->OptionFlags |= (int)HdPackOptions::DontRenderOriginalTiles; + } else { + MessageManager::Log("[HDPack] Invalid option: " + token); + } + } +} + +void HdPackLoader::ProcessConditionTag(vector &tokens, bool createInvertedCondition) +{ + checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters"); + checkConstraint(tokens[0].size() > 0, "[HDPack] Condition name may not be empty"); + checkConstraint(tokens[0].find_last_of('!') == string::npos, "[HDPack] Condition name may not contain '!' characters"); + + unique_ptr condition; + + if(tokens[1] == "tileAtPosition") { + condition.reset(new HdPackTileAtPositionCondition()); + } else if(tokens[1] == "tileNearby") { + condition.reset(new HdPackTileNearbyCondition()); + } else if(tokens[1] == "spriteAtPosition") { + condition.reset(new HdPackSpriteAtPositionCondition()); + } else if(tokens[1] == "spriteNearby") { + condition.reset(new HdPackSpriteNearbyCondition()); + } else if(tokens[1] == "memoryCheck" || tokens[1] == "ppuMemoryCheck") { + condition.reset(new HdPackMemoryCheckCondition()); + } else if(tokens[1] == "memoryCheckConstant" || tokens[1] == "ppuMemoryCheckConstant") { + condition.reset(new HdPackMemoryCheckConstantCondition()); + } else if(tokens[1] == "frameRange") { + condition.reset(new HdPackFrameRangeCondition()); + } else { + MessageManager::Log("[HDPack] Invalid condition type: " + tokens[1]); + return; + } + + tokens[0].erase(tokens[0].find_last_not_of(" \n\r\t") + 1); + condition->Name = tokens[0]; + + if(createInvertedCondition) { + condition->Name = "!" + condition->Name; + } + + int index = 2; + if(dynamic_cast(condition.get())) { + checkConstraint(tokens.size() >= 6, "[HDPack] Condition tag should contain at least 6 parameters"); + + int x = std::stoi(tokens[index++]); + int y = std::stoi(tokens[index++]); + string token = tokens[index++]; + int32_t tileIndex = -1; + string tileData; + if(token.size() == 32) { + tileData = token; + } else { + if(_data->Version < 104) { + tileIndex = std::stoi(token); + } else { + //Tile indexes are stored as hex starting from version 104+ + tileIndex = HexUtilities::FromHex(token); + } + } + uint32_t palette = HexUtilities::FromHex(tokens[index++]); + + ((HdPackBaseTileCondition*)condition.get())->Initialize(x, y, palette, tileIndex, tileData); + } else if(dynamic_cast(condition.get())) { + checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs"); + checkConstraint(tokens.size() >= 5, "[HDPack] Condition tag should contain at least 5 parameters"); + + bool usePpuMemory = tokens[1].substr(0, 3) == "ppu"; + uint32_t operandA = HexUtilities::FromHex(tokens[index++]); + + if(usePpuMemory) { + checkConstraint(operandA <= 0x3FFF, "[HDPack] Out of range memoryCheck operand"); + operandA |= HdPackBaseMemoryCondition::PpuMemoryMarker; + } else { + checkConstraint(operandA <= 0xFFFF, "[HDPack] Out of range memoryCheck operand"); + } + + HdPackConditionOperator op; + string opString = tokens[index++]; + if(opString == "==") { + op = HdPackConditionOperator::Equal; + } else if(opString == "!=") { + op = HdPackConditionOperator::NotEqual; + } else if(opString == ">") { + op = HdPackConditionOperator::GreaterThan; + } else if(opString == "<") { + op = HdPackConditionOperator::LowerThan; + } else if(opString == "<=") { + op = HdPackConditionOperator::LowerThanOrEqual; + } else if(opString == ">=") { + op = HdPackConditionOperator::GreaterThanOrEqual; + } else { + checkConstraint(false, "[HDPack] Invalid operator."); + } + + uint32_t operandB = HexUtilities::FromHex(tokens[index++]); + uint32_t mask = 0xFF; + if(tokens.size() > 5 && _data->Version >= 103) { + checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheck mask"); + mask = HexUtilities::FromHex(tokens[index++]); + } + + if(dynamic_cast(condition.get())) { + if(usePpuMemory) { + checkConstraint(operandB <= 0x3FFF, "[HDPack] Out of range memoryCheck operand"); + operandB |= HdPackBaseMemoryCondition::PpuMemoryMarker; + } else { + checkConstraint(operandB <= 0xFFFF, "[HDPack] Out of range memoryCheck operand"); + } + _data->WatchedMemoryAddresses.emplace(operandB); + } else if(dynamic_cast(condition.get())) { + checkConstraint(operandB <= 0xFF, "[HDPack] Out of range memoryCheckConstant operand"); + } + _data->WatchedMemoryAddresses.emplace(operandA); + ((HdPackBaseMemoryCondition*)condition.get())->Initialize(operandA, op, operandB, (uint8_t)mask); + } else if(dynamic_cast(condition.get())) { + checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs"); + checkConstraint(tokens.size() >= 4, "[HDPack] Condition tag should contain at least 4 parameters"); + + int32_t operandA; + int32_t operandB; + if(_data->Version == 101) { + operandA = HexUtilities::FromHex(tokens[index++]); + operandB = HexUtilities::FromHex(tokens[index++]); + } else { + //Version 102+ + operandA = std::stoi(tokens[index++]); + operandB = std::stoi(tokens[index++]); + } + + checkConstraint(operandA >= 0 && operandA <= 0xFFFF, "[HDPack] Out of range frameRange operand"); + checkConstraint(operandB >= 0 && operandB <= 0xFFFF, "[HDPack] Out of range frameRange operand"); + + ((HdPackFrameRangeCondition*)condition.get())->Initialize(operandA, operandB); + } + + HdPackCondition *cond = condition.get(); + condition.release(); + _data->Conditions.emplace_back(unique_ptr(cond)); + _conditionsByName[cond->Name] = cond; +} + +void HdPackLoader::ProcessBackgroundTag(vector &tokens, vector conditions) +{ + checkConstraint(tokens.size() >= 2, "[HDPack] Background tag should contain at least 2 parameters"); + HdBackgroundFileData* bgFileData = nullptr; + for(unique_ptr &bgData : _data->BackgroundFileData) { + if(bgData->PngName == tokens[0]) { + bgFileData = bgData.get(); + } + } + + if(!bgFileData) { + vector pixelData; + uint32_t width, height; + vector fileContent; + if(LoadFile(tokens[0], fileContent)) { + if(PNGHelper::ReadPNG(fileContent, pixelData, width, height)) { + _data->BackgroundFileData.push_back(unique_ptr(new HdBackgroundFileData())); + bgFileData = _data->BackgroundFileData.back().get(); + bgFileData->PixelData.resize(pixelData.size() / 4); + memcpy(bgFileData->PixelData.data(), pixelData.data(), bgFileData->PixelData.size() * sizeof(bgFileData->PixelData[0])); + PremultiplyAlpha(bgFileData->PixelData); + + bgFileData->Width = width; + bgFileData->Height = height; + bgFileData->PngName = tokens[0]; + } + } + } + + HdBackgroundInfo backgroundInfo; + if(bgFileData) { + backgroundInfo.Data = bgFileData; + if (_data->Version >= 105) { + backgroundInfo.Brightness = (int)(std::stof(tokens[1]) * 255); + } else { + backgroundInfo.Brightness = (uint8_t)(std::stof(tokens[1]) * 255); + } + backgroundInfo.HorizontalScrollRatio = 0; + backgroundInfo.VerticalScrollRatio = 0; + backgroundInfo.Priority = 10; + backgroundInfo.Left = 0; + backgroundInfo.Top = 0; + + for(HdPackCondition* condition : conditions) { + if( + !dynamic_cast(condition) && + !dynamic_cast(condition) && + !dynamic_cast(condition) && + !dynamic_cast(condition) && + !dynamic_cast(condition) + ) { + MessageManager::Log("[HDPack] Invalid condition type for background: " + tokens[0]); + return; + } else { + backgroundInfo.Conditions.push_back(condition); + } + } + + if(tokens.size() > 2) { + checkConstraint(_data->Version >= 101, "[HDPack] This feature requires version 101+ of HD Packs"); + + backgroundInfo.HorizontalScrollRatio = std::stof(tokens[2]); + if(tokens.size() > 3) { + backgroundInfo.VerticalScrollRatio = std::stof(tokens[3]); + } + if(tokens.size() > 4) { + checkConstraint(_data->Version >= 102, "[HDPack] This feature requires version 102+ of HD Packs"); + if(_data->Version >= 106) { + backgroundInfo.Priority = std::stoi(tokens[4]); + checkConstraint(backgroundInfo.Priority >= 0 && backgroundInfo.Priority < 40, "[HDPack] Invalid background priority value"); + } else { + backgroundInfo.Priority = tokens[4] == "Y" ? 0 : 10; + } + } + if(tokens.size() > 6) { + checkConstraint(_data->Version >= 105, "[HDPack] This feature requires version 105+ of HD Packs"); + backgroundInfo.Left = std::max(0, std::stoi(tokens[5])); + backgroundInfo.Top = std::max(0, std::stoi(tokens[6])); + } + } + + _data->Backgrounds.push_back(backgroundInfo); + } else { + MessageManager::Log("[HDPack] Error while loading background: " + tokens[0]); + } +} + +int HdPackLoader::ProcessSoundTrack(string albumString, string trackString, string filename) +{ + int album = std::stoi(albumString); + if(album < 0 || album > 255) { + MessageManager::Log("[HDPack] Invalid album value: " + albumString); + return -1; + } + + int track = std::stoi(trackString); + if(track < 0 || track > 255) { + MessageManager::Log("[HDPack] Invalid track value: " + trackString); + return -1; + } + + if(!CheckFile(filename)) { + MessageManager::Log("[HDPack] OGG file not found: " + filename); + return -1; + } + + return album * 256 + track; +} + +void HdPackLoader::ProcessBgmTag(vector &tokens) +{ + int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]); + if(trackId >= 0) { + if(_loadFromZip) { + VirtualFile file(_hdPackFolder, tokens[2]); + _data->BgmFilesById[trackId] = file; + } else { + _data->BgmFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]); + } + } +} + +void HdPackLoader::ProcessSfxTag(vector &tokens) +{ + int trackId = ProcessSoundTrack(tokens[0], tokens[1], tokens[2]); + if(trackId >= 0) { + if(_loadFromZip) { + VirtualFile file(_hdPackFolder, tokens[2]); + _data->SfxFilesById[trackId] = file; + } else { + _data->SfxFilesById[trackId] = FolderUtilities::CombinePath(_hdPackFolder, tokens[2]); + } + } +} + +vector HdPackLoader::ParseConditionString(string conditionString) +{ + vector conditionNames = StringUtilities::Split(conditionString, '&'); + + vector conditions; + for(string conditionName : conditionNames) { + conditionName.erase(conditionName.find_last_not_of(" \n\r\t") + 1); + + auto result = _conditionsByName.find(conditionName); + if(result != _conditionsByName.end()) { + conditions.push_back(result->second); + } else { + MessageManager::Log("[HDPack] Condition not found: " + conditionName); + } + } + + return conditions; +} + +void HdPackLoader::LoadCustomPalette() +{ + vector fileData; + if(LoadFile("palette.dat", fileData)) { + vector paletteData; + + for(size_t i = 0; i < fileData.size(); i+= 3){ + paletteData.push_back(0xFF000000 | (fileData[i] << 16) | (fileData[i+1] << 8) | fileData[i+2]); + } + + if(paletteData.size() == 0x40) { + _data->Palette = paletteData; + } + } +} + +void HdPackLoader::InitializeHdPack() +{ + for(unique_ptr &tileInfo : _data->Tiles) { + auto tiles = _data->TileByKey.find(tileInfo->GetKey(false)); + if(tiles == _data->TileByKey.end()) { + _data->TileByKey[tileInfo->GetKey(false)] = vector(); + } + _data->TileByKey[tileInfo->GetKey(false)].push_back(tileInfo.get()); + + if(tileInfo->DefaultTile) { + auto tiles = _data->TileByKey.find(tileInfo->GetKey(true)); + if(tiles == _data->TileByKey.end()) { + _data->TileByKey[tileInfo->GetKey(true)] = vector(); + } + _data->TileByKey[tileInfo->GetKey(true)].push_back(tileInfo.get()); + } + } +} diff --git a/Core/NES/HdPacks/HdPackLoader.h b/Core/NES/HdPacks/HdPackLoader.h new file mode 100644 index 00000000..b251a72d --- /dev/null +++ b/Core/NES/HdPacks/HdPackLoader.h @@ -0,0 +1,50 @@ +#pragma once +#include "stdafx.h" +#include "NES/HdPacks/HdData.h" +#include "Utilities/ZipReader.h" +#include "Utilities/VirtualFile.h" + +class HdPackLoader +{ +public: + static bool LoadHdNesPack(string definitionFile, HdPackData &outData); + static bool LoadHdNesPack(VirtualFile &romFile, HdPackData &outData); + +private: + HdPackData* _data; + bool _loadFromZip = false; + ZipReader _reader; + string _hdPackDefinitionFile; + string _hdPackFolder; + vector _hdNesBitmaps; + unordered_map _conditionsByName; + + HdPackLoader(); + + bool InitializeLoader(VirtualFile &romPath, HdPackData *data); + bool LoadFile(string filename, vector &fileData); + bool CheckFile(string filename); + + bool LoadPack(); + void InitializeHdPack(); + void LoadCustomPalette(); + + void InitializeGlobalConditions(); + + //Video + bool ProcessImgTag(string src); + void PremultiplyAlpha(vector& pixelData); + void ProcessPatchTag(vector &tokens); + void ProcessOverscanTag(vector &tokens); + void ProcessConditionTag(vector &tokens, bool createInvertedCondition); + void ProcessTileTag(vector &tokens, vector conditions); + void ProcessBackgroundTag(vector &tokens, vector conditions); + void ProcessOptionTag(vector& tokens); + + //Audio + int ProcessSoundTrack(string albumString, string trackString, string filename); + void ProcessBgmTag(vector &tokens); + void ProcessSfxTag(vector &tokens); + + vector ParseConditionString(string conditionString); +}; \ No newline at end of file diff --git a/Core/NES/HdPacks/HdVideoFilter.cpp b/Core/NES/HdPacks/HdVideoFilter.cpp new file mode 100644 index 00000000..9cb6b894 --- /dev/null +++ b/Core/NES/HdPacks/HdVideoFilter.cpp @@ -0,0 +1,38 @@ +#include "stdafx.h" +#include "NES/HdPacks/HdNesPack.h" +#include "NES/HdPacks/HdVideoFilter.h" +#include "NES/NesConsole.h" +#include "NES/NesConstants.h" +#include "Shared/Video/BaseVideoFilter.h" + +HdVideoFilter::HdVideoFilter(Emulator* emu, HdPackData* hdData) : BaseVideoFilter(emu) +{ + _hdData = hdData; + _hdNesPack.reset(new HdNesPack(hdData)); +} + +FrameInfo HdVideoFilter::GetFrameInfo() +{ + OverscanDimensions overscan = GetOverscan(); + uint32_t hdScale = _hdNesPack->GetScale(); + + return { + (NesConstants::ScreenWidth - overscan.Left - overscan.Right) * hdScale, + (NesConstants::ScreenHeight - overscan.Top - overscan.Bottom) * hdScale + }; +} + +OverscanDimensions HdVideoFilter::GetOverscan() +{ + if(_hdData->HasOverscanConfig) { + return _hdData->Overscan; + } else { + return BaseVideoFilter::GetOverscan(); + } +} + +void HdVideoFilter::ApplyFilter(uint16_t *ppuOutputBuffer) +{ + OverscanDimensions overscan = GetOverscan(); + _hdNesPack->Process((HdScreenInfo*)_frameData, GetOutputBuffer(), overscan); +} \ No newline at end of file diff --git a/Core/NES/HdPacks/HdVideoFilter.h b/Core/NES/HdPacks/HdVideoFilter.h new file mode 100644 index 00000000..977b212f --- /dev/null +++ b/Core/NES/HdPacks/HdVideoFilter.h @@ -0,0 +1,21 @@ +#pragma once +#include "stdafx.h" +#include "Shared/Video/BaseVideoFilter.h" + +class HdNesPack; +class Emulator; +struct HdPackData; + +class HdVideoFilter : public BaseVideoFilter +{ +private: + HdPackData* _hdData; + unique_ptr _hdNesPack = nullptr; + +public: + HdVideoFilter(Emulator* emu, HdPackData* hdData); + + void ApplyFilter(uint16_t *ppuOutputBuffer) override; + FrameInfo GetFrameInfo() override; + OverscanDimensions GetOverscan() override; +}; diff --git a/Core/NES/HdPacks/OggMixer.cpp b/Core/NES/HdPacks/OggMixer.cpp new file mode 100644 index 00000000..c943b737 --- /dev/null +++ b/Core/NES/HdPacks/OggMixer.cpp @@ -0,0 +1,121 @@ +#include "stdafx.h" +#include +#include "NES/HdPacks/OggReader.h" +#include "NES/HdPacks/OggMixer.h" + +enum class OggPlaybackOptions +{ + None = 0x00, + Loop = 0x01 +}; + +OggMixer::OggMixer() +{ +} + +void OggMixer::Reset(uint32_t sampleRate) +{ + _bgm.reset(); + _sfx.clear(); + _sfxVolume = 128; + _bgmVolume = 128; + _options = 0; + _sampleRate = sampleRate; + _paused = false; +} + +void OggMixer::SetPlaybackOptions(uint8_t options) +{ + _options = options; + + bool loop = (options & (int)OggPlaybackOptions::Loop) != 0; + if(_bgm) { + _bgm->SetLoopFlag(loop); + } +} + +void OggMixer::SetPausedFlag(bool paused) +{ + _paused = paused; +} + +void OggMixer::StopBgm() +{ + _bgm.reset(); +} + +void OggMixer::StopSfx() +{ + _sfx.clear(); +} + +void OggMixer::SetBgmVolume(uint8_t volume) +{ + _bgmVolume = volume; +} + +void OggMixer::SetSfxVolume(uint8_t volume) +{ + _sfxVolume = volume; +} + +bool OggMixer::IsBgmPlaying() +{ + return !_paused && _bgm; +} + +bool OggMixer::IsSfxPlaying() +{ + return _sfx.size() > 0; +} + +void OggMixer::SetSampleRate(int sampleRate) +{ + _sampleRate = sampleRate; + if(_bgm) { + _bgm->SetSampleRate(sampleRate); + } + for(shared_ptr &sfx : _sfx) { + sfx->SetSampleRate(sampleRate); + } +} + +bool OggMixer::Play(string filename, bool isSfx, uint32_t startOffset) +{ + shared_ptr reader(new OggReader()); + bool loop = !isSfx && (_options & (int)OggPlaybackOptions::Loop) != 0; + if(reader->Init(filename, loop, _sampleRate, startOffset)) { + if(isSfx) { + _sfx.push_back(reader); + } else { + _bgm = reader; + } + return true; + } + return false; +} + +int OggMixer::GetBgmOffset() +{ + if(_bgm) { + return _bgm->GetOffset(); + } else { + return -1; + } +} + +void OggMixer::MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate) +{ + if(_bgm && !_paused) { + _bgm->SetSampleRate(sampleRate); + _bgm->ApplySamples(out, sampleCount, _bgmVolume); + if(_bgm->IsPlaybackOver()) { + _bgm.reset(); + } + } + for(shared_ptr& sfx : _sfx) { + sfx->SetSampleRate(sampleRate); + sfx->ApplySamples(out, sampleCount, _sfxVolume); + } + _sfx.erase(std::remove_if(_sfx.begin(), _sfx.end(), [](const shared_ptr& o) { return o->IsPlaybackOver(); }), _sfx.end()); +} diff --git a/Core/NES/HdPacks/OggMixer.h b/Core/NES/HdPacks/OggMixer.h new file mode 100644 index 00000000..c9d5a86c --- /dev/null +++ b/Core/NES/HdPacks/OggMixer.h @@ -0,0 +1,37 @@ +#pragma once +#include "stdafx.h" +#include "Shared/Interfaces/IAudioProvider.h" + +class OggReader; + +class OggMixer : public IAudioProvider +{ +private: + shared_ptr _bgm; + vector> _sfx; + + uint32_t _sampleRate; + uint8_t _bgmVolume; + uint8_t _sfxVolume; + uint8_t _options; + bool _paused; + +public: + OggMixer(); + + void SetSampleRate(int sampleRate); + + void Reset(uint32_t sampleRate); + bool Play(string filename, bool isSfx, uint32_t startOffset); + void SetPlaybackOptions(uint8_t options); + void SetPausedFlag(bool paused); + void StopBgm(); + void StopSfx(); + void SetBgmVolume(uint8_t volume); + void SetSfxVolume(uint8_t volume); + bool IsBgmPlaying(); + bool IsSfxPlaying(); + int32_t GetBgmOffset(); + + void MixAudio(int16_t* out, uint32_t sampleCount, uint32_t sampleRate) override; +}; diff --git a/Core/NES/HdPacks/OggReader.cpp b/Core/NES/HdPacks/OggReader.cpp new file mode 100644 index 00000000..e37e13ad --- /dev/null +++ b/Core/NES/HdPacks/OggReader.cpp @@ -0,0 +1,94 @@ +#include "stdafx.h" +#include "NES/HdPacks/OggReader.h" +#include "Utilities/Audio/stb_vorbis.h" + +OggReader::OggReader() +{ + _done = false; + _oggBuffer = new int16_t[10000]; + _outputBuffer = new int16_t[2000]; +} + +OggReader::~OggReader() +{ + delete[] _oggBuffer; + delete[] _outputBuffer; + + if(_vorbis) { + stb_vorbis_close(_vorbis); + } +} + +bool OggReader::Init(string filename, bool loop, uint32_t sampleRate, uint32_t startOffset) +{ + int error; + VirtualFile file = filename; + _fileData = vector(100000); + if(file.ReadFile(_fileData)) { + _vorbis = stb_vorbis_open_memory(_fileData.data(), (int)_fileData.size(), &error, nullptr); + if(_vorbis) { + _loop = loop; + _oggSampleRate = stb_vorbis_get_info(_vorbis).sample_rate; + if(startOffset > 0) { + stb_vorbis_seek(_vorbis, startOffset); + } + return true; + } + } + return false; +} + +bool OggReader::IsPlaybackOver() +{ + return _done; +} + +void OggReader::SetSampleRate(int sampleRate) +{ + if(sampleRate != _sampleRate) { + _sampleRate = sampleRate; + } +} + +void OggReader::SetLoopFlag(bool loop) +{ + _loop = loop; +} + +void OggReader::ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume) +{ + int32_t samplesNeeded = (int32_t)sampleCount - _leftoverSampleCount; + uint32_t samplesRead = 0; + if(samplesNeeded > 0) { + uint32_t samplesToLoad = samplesNeeded * _oggSampleRate / _sampleRate + 2; + uint32_t samplesLoaded = (uint32_t)stb_vorbis_get_samples_short_interleaved(_vorbis, 2, _oggBuffer, samplesToLoad * 2); + if(samplesLoaded < samplesToLoad) { + if(_loop) { + stb_vorbis_seek_start(_vorbis); + samplesLoaded += stb_vorbis_get_samples_short_interleaved(_vorbis, 2, _oggBuffer + samplesLoaded * 2, samplesToLoad - samplesLoaded); + } else { + _done = true; + } + } + _resampler.SetSampleRates(_oggSampleRate, _sampleRate); + samplesRead = _resampler.Resample(_oggBuffer, samplesLoaded, _outputBuffer + _leftoverSampleCount * 2); + } + + + uint32_t samplesToProcess = std::min((uint32_t)sampleCount * 2, (samplesRead + _leftoverSampleCount) * 2); + for(uint32_t i = 0; i < samplesToProcess; i++) { + buffer[i] += (int16_t)((int32_t)_outputBuffer[i] * volume / 255); + } + + //Calculate count of extra samples that couldn't be mixed with the rest of the audio and copy them to the beginning of the buffer + //These will be mixed on the next call to ApplySamples + _leftoverSampleCount = std::max(0, (int32_t)(samplesRead + _leftoverSampleCount) - (int32_t)sampleCount); + for(uint32_t i = 0; i < _leftoverSampleCount * 2; i++) { + _outputBuffer[i] = _outputBuffer[samplesToProcess + i]; + } +} + +uint32_t OggReader::GetOffset() +{ + return stb_vorbis_get_file_offset(_vorbis); +} \ No newline at end of file diff --git a/Core/NES/HdPacks/OggReader.h b/Core/NES/HdPacks/OggReader.h new file mode 100644 index 00000000..aba4b549 --- /dev/null +++ b/Core/NES/HdPacks/OggReader.h @@ -0,0 +1,36 @@ +#pragma once +#include "stdafx.h" +#include "Utilities/VirtualFile.h" +#include "Utilities/Audio/HermiteResampler.h" + +struct stb_vorbis; + +class OggReader +{ +private: + stb_vorbis* _vorbis = nullptr; + int16_t* _outputBuffer = nullptr; + int16_t* _oggBuffer = nullptr; + + HermiteResampler _resampler; + uint32_t _leftoverSampleCount = 0; + + bool _loop = false; + bool _done = false; + + int _sampleRate = 0; + int _oggSampleRate = 0; + + vector _fileData; + +public: + OggReader(); + ~OggReader(); + + bool Init(string filename, bool loop, uint32_t sampleRate, uint32_t startOffset = 0); + bool IsPlaybackOver(); + void SetSampleRate(int sampleRate); + void SetLoopFlag(bool loop); + void ApplySamples(int16_t* buffer, size_t sampleCount, uint8_t volume); + uint32_t GetOffset(); +}; diff --git a/Core/NES/NesConsole.cpp b/Core/NES/NesConsole.cpp index e66826bc..e11dc663 100644 --- a/Core/NES/NesConsole.cpp +++ b/Core/NES/NesConsole.cpp @@ -9,6 +9,11 @@ #include "NES/NesMemoryManager.h" #include "NES/DefaultNesPpu.h" #include "NES/NsfPpu.h" +#include "NES/HdPacks/HdAudioDevice.h" +#include "NES/HdPacks/HdData.h" +#include "NES/HdPacks/HdNesPpu.h" +#include "NES/HdPacks/HdPackLoader.h" +#include "NES/HdPacks/HdVideoFilter.h" #include "NES/NesDefaultVideoFilter.h" #include "NES/NesNtscFilter.h" #include "NES/NesConstants.h" @@ -112,6 +117,8 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile) { RomData romData; + LoadHdPack(romFile); + LoadRomResult result = LoadRomResult::UnknownType; unique_ptr mapper = MapperFactory::InitializeFromFile(this, romFile, romData, result); if(mapper) { @@ -168,29 +175,34 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile) _controlManager->SetPollCounter(pollCounter); _controlManager->UpdateControlDevices(); + _mapper->SetConsole(this); + _mapper->Initialize(romData); + //Re-enable battery saves /*_batteryManager->SetSaveEnabled(true); - + */ if(_hdData && (!_hdData->Tiles.empty() || !_hdData->Backgrounds.empty())) { - _ppu.reset(new HdPpu(shared_from_this(), _hdData.get())); - } else */ - - if(dynamic_cast(_mapper.get())) { + _ppu.reset(new HdNesPpu(this, _hdData.get())); + } else if(dynamic_cast(_mapper.get())) { //Disable most of the PPU for NSFs _ppu.reset(new NsfPpu(this)); } else { _ppu.reset(new DefaultNesPpu(this)); } - _mapper->SetConsole(this); - _mapper->Initialize(romData); - _memoryManager->SetMapper(_mapper.get()); _memoryManager->RegisterIODevice(_ppu.get()); _memoryManager->RegisterIODevice(_apu.get()); _memoryManager->RegisterIODevice(_controlManager.get()); _memoryManager->RegisterIODevice(_mapper.get()); + if(_hdData && (!_hdData->BgmFilesById.empty() || !_hdData->SfxFilesById.empty())) { + _hdAudioDevice.reset(new HdAudioDevice(_emu, _hdData.get())); + _memoryManager->RegisterIODevice(_hdAudioDevice.get()); + } else { + _hdAudioDevice.reset(); + } + UpdateRegion(); _mixer->Reset(); @@ -204,6 +216,23 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile) return result; } +void NesConsole::LoadHdPack(VirtualFile& romFile) +{ + _hdData.reset(); + if(GetNesConfig().EnableHdPacks) { + _hdData.reset(new HdPackData()); + if(!HdPackLoader::LoadHdNesPack(romFile, *_hdData.get())) { + _hdData.reset(); + } else { + auto result = _hdData->PatchesByHash.find(romFile.GetSha1Hash()); + if(result != _hdData->PatchesByHash.end()) { + VirtualFile patchFile = result->second; + romFile.ApplyPatch(patchFile); + } + } + } +} + void NesConsole::UpdateRegion() { ConsoleRegion region = GetNesConfig().Region; @@ -353,11 +382,15 @@ void NesConsole::SaveBattery() BaseVideoFilter* NesConsole::GetVideoFilter() { - VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter; - if(filterType == VideoFilterType::NTSC) { - return new NesNtscFilter(_emu); + if(_hdData) { + return new HdVideoFilter(_emu, _hdData.get()); } else { - return new NesDefaultVideoFilter(_emu); + VideoFilterType filterType = _emu->GetSettings()->GetVideoConfig().VideoFilter; + if(filterType == VideoFilterType::NTSC) { + return new NesNtscFilter(_emu); + } else { + return new NesDefaultVideoFilter(_emu); + } } } diff --git a/Core/NES/NesConsole.h b/Core/NES/NesConsole.h index 598e5375..e9e7080f 100644 --- a/Core/NES/NesConsole.h +++ b/Core/NES/NesConsole.h @@ -15,6 +15,8 @@ class BaseMapper; class EmuSettings; class NesSoundMixer; class BaseVideoFilter; +class HdAudioDevice; +struct HdPackData; enum class DebugEventType; enum class EventType; @@ -36,9 +38,13 @@ private: shared_ptr _controlManager; unique_ptr _mixer; + unique_ptr _hdData; + unique_ptr _hdAudioDevice; + ConsoleRegion _region = ConsoleRegion::Auto; void UpdateRegion(); + void LoadHdPack(VirtualFile& romFile); public: NesConsole(Emulator* emulator); diff --git a/Core/NES/NesPpu.cpp b/Core/NES/NesPpu.cpp index 71e96b47..8951e1fb 100644 --- a/Core/NES/NesPpu.cpp +++ b/Core/NES/NesPpu.cpp @@ -13,6 +13,7 @@ #include "NES/DefaultNesPpu.h" #include "NES/NsfPpu.h" +#include "NES/HdPacks/HdNesPpu.h" #include "Debugger/Debugger.h" #include "Shared/EmuSettings.h" @@ -716,6 +717,8 @@ template void NesPpu::LoadTileInfo() if(IsRenderingEnabled()) { switch(_cycle & 0x07) { case 1: { + ((T*)this)->StoreTileInformation(); //Used by HD packs + _previousTilePalette = _currentTilePalette; _currentTilePalette = _tile.PaletteOffset; @@ -724,7 +727,6 @@ template void NesPpu::LoadTileInfo() uint8_t tileIndex = ReadVram(GetNameTableAddr()); _tile.TileAddr = (tileIndex << 4) | (_videoRamAddr >> 12) | _backgroundPatternAddr; - //_nextTile.OffsetY = _state.VideoRamAddr >> 12; break; } @@ -736,7 +738,6 @@ template void NesPpu::LoadTileInfo() case 5: _tile.LowByte = ReadVram(_tile.TileAddr); - ((T*)this)->StoreTileAbsoluteAddress(); break; case 7: @@ -771,7 +772,6 @@ template void NesPpu::LoadSprite(uint8_t spriteY, uint8_t tileIndex, NesSpriteInfo &info = _spriteTiles[_spriteIndex]; info.BackgroundPriority = backgroundPriority; info.HorizontalMirror = horizontalMirror; - //info.VerticalMirror = verticalMirror; info.PaletteOffset = ((attributes & 0x03) << 2) | 0x10; if(extraSprite) { //Use DebugReadVram for extra sprites to prevent side-effects. @@ -782,10 +782,8 @@ template void NesPpu::LoadSprite(uint8_t spriteY, uint8_t tileIndex, info.LowByte = ReadVram(tileAddr); info.HighByte = ReadVram(tileAddr + 8); } - //info.TileAddr = tileAddr; - //info.OffsetY = lineOffset; info.SpriteX = spriteX; - ((T*)this)->StoreSpriteAbsoluteAddress(); + ((T*)this)->StoreSpriteInformation(verticalMirror, tileAddr, lineOffset); //Used by HD packs if(_scanline >= 0) { //Sprites read on prerender scanline are not shown on scanline 0 @@ -1215,12 +1213,14 @@ template void NesPpu::SendFrame() { UpdateGrayscaleAndIntensifyBits(); + void* frameData = ((T*)this)->OnBeforeSendFrame(); + if(_console->IsVsMainConsole()) { _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuFrameDone, _currentOutputBuffer); } #ifdef LIBRETRO - _console->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, true, false); + _console->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, true, false, frameData); #else if(_console->GetVsMainConsole() || _console->GetVsSubConsole()) { SendFrameVsDualSystem(); @@ -1229,7 +1229,7 @@ template void NesPpu::SendFrame() } } else { bool forRewind = _emu->GetRewindManager()->IsRewinding(); - _emu->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, forRewind, forRewind); + _emu->GetVideoDecoder()->UpdateFrame(_currentOutputBuffer, NesConstants::ScreenWidth, NesConstants::ScreenHeight, _frameCount, forRewind, forRewind, frameData); _emu->ProcessEndOfFrame(); } @@ -1619,3 +1619,8 @@ template NesPpu::NesPpu(NesConsole* console); template uint16_t* NesPpu::GetScreenBuffer(bool previousBuffer); template void NesPpu::Exec(); template uint32_t NesPpu::GetPixelBrightness(uint8_t x, uint8_t y); + +template NesPpu::NesPpu(NesConsole* console); +template uint16_t* NesPpu::GetScreenBuffer(bool previousBuffer); +template void NesPpu::Exec(); +template uint32_t NesPpu::GetPixelBrightness(uint8_t x, uint8_t y); diff --git a/Core/NES/NesPpu.h b/Core/NES/NesPpu.h index 4958d396..3f0e9d6a 100644 --- a/Core/NES/NesPpu.h +++ b/Core/NES/NesPpu.h @@ -81,7 +81,7 @@ protected: __forceinline uint8_t GetPixelColor(); void UpdateGrayscaleAndIntensifyBits(); - virtual void SendFrame(); + void SendFrame(); void SendFrameVsDualSystem(); diff --git a/Core/NES/NesSoundMixer.cpp b/Core/NES/NesSoundMixer.cpp index 05cd5f0d..a53b5e25 100644 --- a/Core/NES/NesSoundMixer.cpp +++ b/Core/NES/NesSoundMixer.cpp @@ -14,7 +14,6 @@ NesSoundMixer::NesSoundMixer(NesConsole* console) _clockRate = 0; _console = console; _mixer = console->GetEmulator()->GetSoundMixer().get(); - //_oggMixer.reset(); _outputBuffer = new int16_t[NesSoundMixer::MaxSamplesPerFrame]; _blipBufLeft = blip_new(NesSoundMixer::MaxSamplesPerFrame); _blipBufRight = blip_new(NesSoundMixer::MaxSamplesPerFrame); @@ -45,10 +44,6 @@ void NesSoundMixer::Serialize(Serializer& s) void NesSoundMixer::Reset() { - //TODO - /*if(_oggMixer) { - _oggMixer->Reset(_settings->GetSampleRate()); - }*/ _sampleCount = 0; _previousOutputLeft = 0; @@ -92,11 +87,7 @@ void NesSoundMixer::PlayAudioBuffer(uint32_t time) } //TODO - /*_console->GetMapper()->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume()); - - if(_oggMixer) { - _oggMixer->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume()); - }*/ + //_console->GetMapper()->ApplySamples(_outputBuffer, sampleCount, _settings->GetMasterVolume()); NesConfig& cfg = _console->GetNesConfig(); if(_console->GetVsSubConsole()) { @@ -157,10 +148,6 @@ void NesSoundMixer::UpdateRates(bool forceUpdate) blip_set_rates(_blipBufLeft, _clockRate, _sampleRate); blip_set_rates(_blipBufRight, _clockRate, _sampleRate); - //TODO - /*if(_oggMixer) { - _oggMixer->SetSampleRate(_sampleRate); - }*/ } NesConfig& cfg = _console->GetNesConfig(); @@ -245,14 +232,3 @@ void NesSoundMixer::EndFrame(uint32_t time) memset(_channelOutput, 0, sizeof(_channelOutput)); } -//TODO -/* -OggMixer* NesSoundMixer::GetOggMixer() -{ - if(!_oggMixer) { - _oggMixer.reset(new OggMixer()); - _oggMixer->Reset(_settings->GetSampleRate()); - } - return _oggMixer.get(); -} -*/ \ No newline at end of file diff --git a/Core/NES/NesSoundMixer.h b/Core/NES/NesSoundMixer.h index bc768797..170aab03 100644 --- a/Core/NES/NesSoundMixer.h +++ b/Core/NES/NesSoundMixer.h @@ -27,9 +27,6 @@ private: EmuSettings* _settings = nullptr; SoundMixer* _mixer = nullptr; - //TODO - //unique_ptr _oggMixer; - StereoPanningFilter _stereoPanning; StereoDelayFilter _stereoDelay; StereoCombFilter _stereoCombFilter; @@ -71,7 +68,5 @@ public: void PlayAudioBuffer(uint32_t cycle); void AddDelta(AudioChannel channel, uint32_t time, int16_t delta); - //OggMixer* GetOggMixer(); - void Serialize(Serializer& s) override; }; \ No newline at end of file diff --git a/Core/NES/NsfPpu.h b/Core/NES/NsfPpu.h index 01ef36d9..7e5e0bbb 100644 --- a/Core/NES/NsfPpu.h +++ b/Core/NES/NsfPpu.h @@ -25,6 +25,11 @@ public: { } + void* OnBeforeSendFrame() + { + return nullptr; + } + void Run(uint64_t runTo) override { do { diff --git a/Core/Shared/SaveStateManager.cpp b/Core/Shared/SaveStateManager.cpp index 06150db2..7ac85f9d 100644 --- a/Core/Shared/SaveStateManager.cpp +++ b/Core/Shared/SaveStateManager.cpp @@ -346,7 +346,7 @@ int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* png unique_ptr filter(_emu->GetVideoFilter()); filter->SetBaseFrameInfo(baseFrameInfo); FrameInfo frameInfo = filter->GetFrameInfo(); - filter->SendFrame((uint16_t*)frameData.data(), 0); + filter->SendFrame((uint16_t*)frameData.data(), 0, nullptr); std::stringstream pngStream; PNGHelper::WritePNG(pngStream, filter->GetOutputBuffer(), frameInfo.Width, frameInfo.Height); diff --git a/Core/Shared/Video/BaseVideoFilter.cpp b/Core/Shared/Video/BaseVideoFilter.cpp index cdcf7e1b..592dd2ba 100644 --- a/Core/Shared/Video/BaseVideoFilter.cpp +++ b/Core/Shared/Video/BaseVideoFilter.cpp @@ -67,11 +67,12 @@ uint32_t BaseVideoFilter::GetBufferSize() return _bufferSize * sizeof(uint32_t); } -void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber) +void BaseVideoFilter::SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, void* frameData) { _frameLock.Acquire(); _overscan = _emu->GetSettings()->GetOverscan(); _isOddFrame = frameNumber % 2; + _frameData = frameData; UpdateBufferSize(); OnBeforeApplyFilter(); ApplyFilter(ppuOutputBuffer); diff --git a/Core/Shared/Video/BaseVideoFilter.h b/Core/Shared/Video/BaseVideoFilter.h index d34b6df2..9ffb61ea 100644 --- a/Core/Shared/Video/BaseVideoFilter.h +++ b/Core/Shared/Video/BaseVideoFilter.h @@ -12,14 +12,15 @@ private: double _yiqToRgbMatrix[6] = {}; uint32_t _bufferSize = 0; SimpleLock _frameLock; - OverscanDimensions _overscan; - bool _isOddFrame; + OverscanDimensions _overscan = {}; + bool _isOddFrame = false; void UpdateBufferSize(); protected: - Emulator* _emu; - FrameInfo _baseFrameInfo; + Emulator* _emu = nullptr; + FrameInfo _baseFrameInfo = {}; + void* _frameData = nullptr; void InitConversionMatrix(double hueShift, double saturationShift); void RgbToYiq(double r, double g, double b, double& y, double& i, double& q); @@ -36,7 +37,7 @@ public: virtual ~BaseVideoFilter(); uint32_t* GetOutputBuffer(); - void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber); + void SendFrame(uint16_t *ppuOutputBuffer, uint32_t frameNumber, void* frameData); void TakeScreenshot(string romName, VideoFilterType filterType); void TakeScreenshot(VideoFilterType filterType, string filename, std::stringstream *stream = nullptr); diff --git a/Core/Shared/Video/DebugHud.cpp b/Core/Shared/Video/DebugHud.cpp index 21cd2321..b139b67f 100644 --- a/Core/Shared/Video/DebugHud.cpp +++ b/Core/Shared/Video/DebugHud.cpp @@ -24,11 +24,11 @@ void DebugHud::ClearScreen() _commands.clear(); } -void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t lineWidth, uint32_t frameNumber) +void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber) { auto lock = _commandLock.AcquireSafe(); for(unique_ptr &command : _commands) { - command->Draw(argbBuffer, frameInfo, overscan, lineWidth, frameNumber); + command->Draw(argbBuffer, frameInfo, overscan, frameNumber); } _commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr& c) { return c->Expired(); }), _commands.end()); } diff --git a/Core/Shared/Video/DebugHud.h b/Core/Shared/Video/DebugHud.h index 6ed7ed0c..05e25acc 100644 --- a/Core/Shared/Video/DebugHud.h +++ b/Core/Shared/Video/DebugHud.h @@ -16,7 +16,7 @@ public: DebugHud(); ~DebugHud(); - void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t width, uint32_t frameNumber); + void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber); void ClearScreen(); void DrawPixel(int x, int y, int color, int frameCount, int startFrame = -1); diff --git a/Core/Shared/Video/DrawCommand.h b/Core/Shared/Video/DrawCommand.h index a06ee7c5..7c9ec0b8 100644 --- a/Core/Shared/Video/DrawCommand.h +++ b/Core/Shared/Video/DrawCommand.h @@ -9,7 +9,6 @@ private: uint32_t* _argbBuffer; FrameInfo _frameInfo; OverscanDimensions _overscan; - uint32_t _lineWidth; int32_t _startFrame; protected: @@ -20,18 +19,19 @@ protected: virtual void InternalDraw() = 0; __forceinline void DrawPixel(uint32_t x, uint32_t y, int color) { - if(x < _overscan.Left || x - _overscan.Left >= _frameInfo.Width || y < _overscan.Top || y - _overscan.Top >= _frameInfo.Height) { - //Out of bounds, skip drawing - return; - } - uint32_t alpha = (color & 0xFF000000); if(alpha > 0) { if(_yScale == 1) { + int32_t offset = ((int32_t)y - _overscan.Top) * _frameInfo.Width + ((int32_t)x - _overscan.Left); + if(offset < 0 || offset >= (int32_t)(_frameInfo.Width * _frameInfo.Height)) { + //Out of bounds, skip drawing + return; + } + if(alpha != 0xFF000000) { - BlendColors((uint8_t*)&_argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)], (uint8_t*)&color); + BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color); } else { - _argbBuffer[(y - _overscan.Top)*_lineWidth + (x - _overscan.Left)] = color; + _argbBuffer[offset] = color; } } else { int xPixelCount = _useIntegerScaling ? _yScale : (int)((x + 1)*_xScale) - (int)(x*_xScale); @@ -42,10 +42,16 @@ protected: for(int i = 0; i < _yScale; i++) { for(int j = 0; j < xPixelCount; j++) { + int32_t offset = ((int32_t)y - top) * _frameInfo.Width + i * _frameInfo.Width + ((int32_t)x - left) + j; + if(offset < 0 || offset >= (int32_t)(_frameInfo.Width * _frameInfo.Height)) { + //Out of bounds, skip drawing + continue; + } + if(alpha != 0xFF000000) { - BlendColors((uint8_t*)&_argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left)+j], (uint8_t*)&color); + BlendColors((uint8_t*)&_argbBuffer[offset], (uint8_t*)&color); } else { - _argbBuffer[(y - top)*_lineWidth + i*_lineWidth + (x - left) +j] = color; + _argbBuffer[offset] = color; } } } @@ -75,7 +81,7 @@ public: { } - void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t lineWidth, uint32_t frameNumber) + void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber) { if(_startFrame < 0) { //When no start frame was specified, start on the next drawn frame @@ -86,9 +92,8 @@ public: _argbBuffer = argbBuffer; _frameInfo = frameInfo; _overscan = overscan; - _lineWidth = lineWidth; - _yScale = lineWidth >= 512 ? 2 : 1; - _xScale = lineWidth >= 512 ? 2.0f : 1.0f; + _yScale = _frameInfo.Width >= 512 ? 2 : 1; + _xScale = _frameInfo.Width >= 512 ? 2.0f : 1.0f; InternalDraw(); diff --git a/Core/Shared/Video/VideoDecoder.cpp b/Core/Shared/Video/VideoDecoder.cpp index 2c8a635c..b808689b 100644 --- a/Core/Shared/Video/VideoDecoder.cpp +++ b/Core/Shared/Video/VideoDecoder.cpp @@ -89,14 +89,14 @@ void VideoDecoder::DecodeFrame(bool forRewind) UpdateVideoFilter(); _videoFilter->SetBaseFrameInfo(_baseFrameInfo); - _videoFilter->SendFrame(_ppuOutputBuffer, _frameNumber); + _videoFilter->SendFrame(_ppuOutputBuffer, _frameNumber, _frameData); uint32_t* outputBuffer = _videoFilter->GetOutputBuffer(); FrameInfo frameInfo = _videoFilter->GetFrameInfo(); _inputHud->DrawControllers(_videoFilter->GetOverscan(), _frameNumber); _systemHud->Draw(frameInfo, _videoFilter->GetOverscan()); - _emu->GetDebugHud()->Draw(outputBuffer, frameInfo, _videoFilter->GetOverscan(), frameInfo.Width, _frameNumber); + _emu->GetDebugHud()->Draw(outputBuffer, frameInfo, _videoFilter->GetOverscan(), _frameNumber); if(_scaleFilter) { outputBuffer = _scaleFilter->ApplyFilter(outputBuffer, frameInfo.Width, frameInfo.Height, _emu->GetSettings()->GetVideoConfig().ScanlineIntensity); @@ -139,7 +139,7 @@ uint32_t VideoDecoder::GetFrameCount() return _frameCount; } -void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind) +void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind, void* frameData) { if(_emu->IsRunAheadFrame()) { return; @@ -159,6 +159,7 @@ void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16 _baseFrameInfo.Height = height; _frameNumber = frameNumber; _ppuOutputBuffer = ppuOutputBuffer; + _frameData = frameData; if(sync) { DecodeFrame(forRewind); } else { @@ -171,7 +172,10 @@ void VideoDecoder::UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16 void VideoDecoder::StartThread() { #ifndef LIBRETRO - if(!_decodeThread) { + if(!_decodeThread) { + _videoFilter.reset(); + UpdateVideoFilter(); + _videoFilter->SetBaseFrameInfo(_baseFrameInfo); _stopFlag = false; _frameChanged = false; _frameCount = 0; diff --git a/Core/Shared/Video/VideoDecoder.h b/Core/Shared/Video/VideoDecoder.h index d88bf527..44548cb2 100644 --- a/Core/Shared/Video/VideoDecoder.h +++ b/Core/Shared/Video/VideoDecoder.h @@ -18,6 +18,7 @@ private: shared_ptr _emu; uint16_t *_ppuOutputBuffer = nullptr; + void* _frameData = nullptr; uint32_t _frameNumber = 0; ConsoleType _consoleType = ConsoleType::Snes; @@ -58,7 +59,7 @@ public: FrameInfo GetFrameInfo(); ScreenSize GetScreenSize(bool ignoreScale); - void UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind); + void UpdateFrame(uint16_t *ppuOutputBuffer, uint16_t width, uint16_t height, uint32_t frameNumber, bool sync, bool forRewind, void* frameData = nullptr); bool IsRunning(); void StartThread(); diff --git a/Core/Shared/Video/VideoRenderer.cpp b/Core/Shared/Video/VideoRenderer.cpp index dfc20ec1..a16f0d3e 100644 --- a/Core/Shared/Video/VideoRenderer.cpp +++ b/Core/Shared/Video/VideoRenderer.cpp @@ -13,7 +13,6 @@ VideoRenderer::VideoRenderer(shared_ptr emu) { _emu = emu; _stopFlag = false; - StartThread(); } VideoRenderer::~VideoRenderer() diff --git a/Utilities/PNGHelper.cpp b/Utilities/PNGHelper.cpp index 75fb41da..2c781a40 100644 --- a/Utilities/PNGHelper.cpp +++ b/Utilities/PNGHelper.cpp @@ -3,6 +3,9 @@ #include "PNGHelper.h" #include "miniz.h" +#define SPNG_USE_MINIZ +#include "spng.h" + bool PNGHelper::WritePNG(std::stringstream &stream, uint32_t* buffer, uint32_t xSize, uint32_t ySize, uint32_t bitsPerPixel) { size_t pngSize = 0; @@ -51,10 +54,10 @@ bool PNGHelper::ReadPNG(vector input, vector &output, uint32_t if(DecodePNG(output, width, height, input.data(), input.size()) == 0) { uint32_t *pngDataPtr = (uint32_t*)output.data(); - for(size_t i = 0, len = output.size() / 4; i < len; i++) { + /*for(size_t i = 0, len = output.size() / 4; i < len; i++) { //ABGR to ARGB pngDataPtr[i] = (pngDataPtr[i] & 0xFF00FF00) | ((pngDataPtr[i] & 0xFF0000) >> 16) | ((pngDataPtr[i] & 0xFF) << 16); - } + }*/ pngWidth = width; pngHeight = height; @@ -83,533 +86,42 @@ bool PNGHelper::ReadPNG(string filename, vector &pngData, uint32_t &png return false; } -/* -decodePNG: The picoPNG function, decodes a PNG file buffer in memory, into a raw pixel buffer. -out_image: output parameter, this will contain the raw pixels after decoding. - By default the output is 32-bit RGBA color. - The std::vector is automatically resized to the correct size. -image_width: output_parameter, this will contain the width of the image in pixels. -image_height: output_parameter, this will contain the height of the image in pixels. -in_png: pointer to the buffer of the PNG file in memory. To get it from a file on - disk, load it and store it in a memory buffer yourself first. -in_size: size of the input PNG file in bytes. -convert_to_rgba32: optional parameter, true by default. - Set to true to get the output in RGBA 32-bit (8 bit per channel) color format - no matter what color type the original PNG image had. This gives predictable, - useable data from any random input PNG. - Set to false to do no color conversion at all. The result then has the same data - type as the PNG image, which can range from 1 bit to 64 bits per pixel. - Information about the color type or palette colors are not provided. You need - to know this information yourself to be able to use the data so this only - works for trusted PNG files. Use LodePNG instead of picoPNG if you need this information. -return: 0 if success, not 0 if some error occured. -*/ int PNGHelper::DecodePNG(vector& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32) { - // picoPNG version 20101224 - // Copyright (c) 2005-2010 Lode Vandevenne - // - // This software is provided 'as-is', without any express or implied - // warranty. In no event will the authors be held liable for any damages - // arising from the use of this software. - // - // Permission is granted to anyone to use this software for any purpose, - // including commercial applications, and to alter it and redistribute it - // freely, subject to the following restrictions: - // - // 1. The origin of this software must not be misrepresented; you must not - // claim that you wrote the original software. If you use this software - // in a product, an acknowledgment in the product documentation would be - // appreciated but is not required. - // 2. Altered source versions must be plainly marked as such, and must not be - // misrepresented as being the original software. - // 3. This notice may not be removed or altered from any source distribution. - - // picoPNG is a PNG decoder in one C++ function of around 500 lines. Use picoPNG for - // programs that need only 1 .cpp file. Since it's a single function, it's very limited, - // it can convert a PNG to raw pixel data either converted to 32-bit RGBA color or - // with no color conversion at all. For anything more complex, another tiny library - // is available: LodePNG (lodepng.c(pp)), which is a single source and header file. - // Apologies for the compact code style, it's to make this tiny. - - static const unsigned long LENBASE[29] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258}; - static const unsigned long LENEXTRA[29] = {0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; - static const unsigned long DISTBASE[30] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577}; - static const unsigned long DISTEXTRA[30] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; - static const unsigned long CLCL[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; //code length code lengths - struct Zlib //nested functions for zlib decompression - { - static unsigned long readBitFromStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (bitp & 0x7)) & 1; bitp++; return result;} - static unsigned long readBitsFromStream(size_t& bitp, const unsigned char* bits, size_t nbits) - { - unsigned long result = 0; - for(size_t i = 0; i < nbits; i++) result += (readBitFromStream(bitp, bits)) << i; - return result; - } - struct HuffmanTree - { - int makeFromLengths(const std::vector& bitlen, unsigned long maxbitlen) - { //make tree given the lengths - unsigned long numcodes = (unsigned long)(bitlen.size()), treepos = 0, nodefilled = 0; - std::vector tree1d(numcodes), blcount(maxbitlen + 1, 0), nextcode(maxbitlen + 1, 0); - for(unsigned long bits = 0; bits < numcodes; bits++) blcount[bitlen[bits]]++; //count number of instances of each code length - for(unsigned long bits = 1; bits <= maxbitlen; bits++) nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1; - for(unsigned long n = 0; n < numcodes; n++) if(bitlen[n] != 0) tree1d[n] = nextcode[bitlen[n]]++; //generate all the codes - tree2d.clear(); tree2d.resize(numcodes * 2, 32767); //32767 here means the tree2d isn't filled there yet - for(unsigned long n = 0; n < numcodes; n++) //the codes - for(unsigned long i = 0; i < bitlen[n]; i++) //the bits for this code - { - unsigned long bit = (tree1d[n] >> (bitlen[n] - i - 1)) & 1; - if(treepos > numcodes - 2) return 55; - if(tree2d[2 * treepos + bit] == 32767) //not yet filled in - { - if(i + 1 == bitlen[n]) { tree2d[2 * treepos + bit] = n; treepos = 0; } //last bit - else { tree2d[2 * treepos + bit] = ++nodefilled + numcodes; treepos = nodefilled; } //addresses are encoded as values > numcodes - } - else treepos = tree2d[2 * treepos + bit] - numcodes; //subtract numcodes from address to get address value - } - return 0; - } - int decode(bool& decoded, unsigned long& result, size_t& treepos, unsigned long bit) const - { //Decodes a symbol from the tree - unsigned long numcodes = (unsigned long)tree2d.size() / 2; - if(treepos >= numcodes) return 11; //error: you appeared outside the codetree - result = tree2d[2 * treepos + bit]; - decoded = (result < numcodes); - treepos = decoded ? 0 : result - numcodes; - return 0; - } - std::vector tree2d; //2D representation of a huffman tree: The one dimension is "0" or "1", the other contains all nodes and leaves of the tree. - }; - struct Inflator - { - int error; - void inflate(std::vector& out, const std::vector& in, size_t inpos = 0) - { - size_t bp = 0, pos = 0; //bit pointer and byte pointer - error = 0; - unsigned long BFINAL = 0; - while(!BFINAL && !error) - { - if(bp >> 3 >= in.size()) { error = 52; return; } //error, bit pointer will jump past memory - BFINAL = readBitFromStream(bp, &in[inpos]); - unsigned long BTYPE = readBitFromStream(bp, &in[inpos]); BTYPE += 2 * readBitFromStream(bp, &in[inpos]); - if(BTYPE == 3) { error = 20; return; } //error: invalid BTYPE - else if(BTYPE == 0) inflateNoCompression(out, &in[inpos], bp, pos, in.size()); - else inflateHuffmanBlock(out, &in[inpos], bp, pos, in.size(), BTYPE); - } - if(!error) out.resize(pos); //Only now we know the true size of out, resize it to that - } - void generateFixedTrees(HuffmanTree& tree, HuffmanTree& treeD) //get the tree of a deflated block with fixed tree - { - std::vector bitlen(288, 8), bitlenD(32, 5);; - for(size_t i = 144; i <= 255; i++) bitlen[i] = 9; - for(size_t i = 256; i <= 279; i++) bitlen[i] = 7; - tree.makeFromLengths(bitlen, 15); - treeD.makeFromLengths(bitlenD, 15); - } - HuffmanTree codetree, codetreeD, codelengthcodetree; //the code tree for Huffman codes, dist codes, and code length codes - unsigned long huffmanDecodeSymbol(const unsigned char* in, size_t& bp, const HuffmanTree& codetree, size_t inlength) - { //decode a single symbol from given list of bits with given code tree. return value is the symbol - bool decoded; unsigned long ct; - for(size_t treepos = 0;;) - { - if((bp & 0x07) == 0 && (bp >> 3) > inlength) { error = 10; return 0; } //error: end reached without endcode - error = codetree.decode(decoded, ct, treepos, readBitFromStream(bp, in)); if(error) return 0; //stop, an error happened - if(decoded) return ct; - } - } - void getTreeInflateDynamic(HuffmanTree& tree, HuffmanTree& treeD, const unsigned char* in, size_t& bp, size_t inlength) - { //get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree - std::vector bitlen(288, 0), bitlenD(32, 0); - if(bp >> 3 >= inlength - 2) { error = 49; return; } //the bit pointer is or will go past the memory - size_t HLIT = readBitsFromStream(bp, in, 5) + 257; //number of literal/length codes + 257 - size_t HDIST = readBitsFromStream(bp, in, 5) + 1; //number of dist codes + 1 - size_t HCLEN = readBitsFromStream(bp, in, 4) + 4; //number of code length codes + 4 - std::vector codelengthcode(19); //lengths of tree to decode the lengths of the dynamic tree - for(size_t i = 0; i < 19; i++) codelengthcode[CLCL[i]] = (i < HCLEN) ? readBitsFromStream(bp, in, 3) : 0; - error = codelengthcodetree.makeFromLengths(codelengthcode, 7); if(error) return; - size_t i = 0, replength; - while(i < HLIT + HDIST) - { - unsigned long code = huffmanDecodeSymbol(in, bp, codelengthcodetree, inlength); if(error) return; - if(code <= 15) { if(i < HLIT) bitlen[i++] = code; else bitlenD[i++ - HLIT] = code; } //a length code - else if(code == 16) //repeat previous - { - if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory - replength = 3 + readBitsFromStream(bp, in, 2); - unsigned long value; //set value to the previous code - if((i - 1) < HLIT) value = bitlen[i - 1]; - else value = bitlenD[i - HLIT - 1]; - for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths - { - if(i >= HLIT + HDIST) { error = 13; return; } //error: i is larger than the amount of codes - if(i < HLIT) bitlen[i++] = value; else bitlenD[i++ - HLIT] = value; - } - } - else if(code == 17) //repeat "0" 3-10 times - { - if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory - replength = 3 + readBitsFromStream(bp, in, 3); - for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths - { - if(i >= HLIT + HDIST) { error = 14; return; } //error: i is larger than the amount of codes - if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; - } - } - else if(code == 18) //repeat "0" 11-138 times - { - if(bp >> 3 >= inlength) { error = 50; return; } //error, bit pointer jumps past memory - replength = 11 + readBitsFromStream(bp, in, 7); - for(size_t n = 0; n < replength; n++) //repeat this value in the next lengths - { - if(i >= HLIT + HDIST) { error = 15; return; } //error: i is larger than the amount of codes - if(i < HLIT) bitlen[i++] = 0; else bitlenD[i++ - HLIT] = 0; - } - } - else { error = 16; return; } //error: somehow an unexisting code appeared. This can never happen. - } - if(bitlen[256] == 0) { error = 64; return; } //the length of the end code 256 must be larger than 0 - error = tree.makeFromLengths(bitlen, 15); if(error) return; //now we've finally got HLIT and HDIST, so generate the code trees, and the function is done - error = treeD.makeFromLengths(bitlenD, 15); if(error) return; - } - void inflateHuffmanBlock(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength, unsigned long btype) - { - if(btype == 1) { generateFixedTrees(codetree, codetreeD); } - else if(btype == 2) { getTreeInflateDynamic(codetree, codetreeD, in, bp, inlength); if(error) return; } - for(;;) - { - unsigned long code = huffmanDecodeSymbol(in, bp, codetree, inlength); if(error) return; - if(code == 256) return; //end code - else if(code <= 255) //literal symbol - { - if(pos >= out.size()) out.resize((pos + 1) * 2); //reserve more room - out[pos++] = (unsigned char)(code); - } - else if(code >= 257 && code <= 285) //length code - { - size_t length = LENBASE[code - 257], numextrabits = LENEXTRA[code - 257]; - if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory - length += readBitsFromStream(bp, in, numextrabits); - unsigned long codeD = huffmanDecodeSymbol(in, bp, codetreeD, inlength); if(error) return; - if(codeD > 29) { error = 18; return; } //error: invalid dist code (30-31 are never used) - unsigned long dist = DISTBASE[codeD], numextrabitsD = DISTEXTRA[codeD]; - if((bp >> 3) >= inlength) { error = 51; return; } //error, bit pointer will jump past memory - dist += readBitsFromStream(bp, in, numextrabitsD); - size_t start = pos, back = start - dist; //backwards - if(pos + length >= out.size()) out.resize((pos + length) * 2); //reserve more room - for(size_t i = 0; i < length; i++) { out[pos++] = out[back++]; if(back >= start) back = start - dist; } - } - } - } - void inflateNoCompression(std::vector& out, const unsigned char* in, size_t& bp, size_t& pos, size_t inlength) - { - while((bp & 0x7) != 0) bp++; //go to first boundary of byte - size_t p = bp / 8; - if(p >= inlength - 4) { error = 52; return; } //error, bit pointer will jump past memory - unsigned long LEN = in[p] + 256 * in[p + 1], NLEN = in[p + 2] + 256 * in[p + 3]; p += 4; - if(LEN + NLEN != 65535) { error = 21; return; } //error: NLEN is not one's complement of LEN - if(pos + LEN >= out.size()) out.resize(pos + LEN); - if(p + LEN > inlength) { error = 23; return; } //error: reading outside of in buffer - for(unsigned long n = 0; n < LEN; n++) out[pos++] = in[p++]; //read LEN bytes of literal data - bp = p * 8; - } - }; - int decompress(std::vector& out, const std::vector& in) //returns error value - { - Inflator inflator; - if(in.size() < 2) { return 53; } //error, size of zlib data too small - if((in[0] * 256 + in[1]) % 31 != 0) { return 24; } //error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way - unsigned long CM = in[0] & 15, CINFO = (in[0] >> 4) & 15, FDICT = (in[1] >> 5) & 1; - if(CM != 8 || CINFO > 7) { return 25; } //error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec - if(FDICT != 0) { return 26; } //error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary." - inflator.inflate(out, in, 2); - return inflator.error; //note: adler32 checksum was skipped and ignored - } - }; - struct PNG //nested functions for PNG decoding - { - struct Info - { - unsigned long width, height, colorType, bitDepth, compressionMethod, filterMethod, interlaceMethod, key_r, key_g, key_b; - bool key_defined; //is a transparent color key given? - std::vector palette; - } info; - int error; - void decode(std::vector& out, const unsigned char* in, size_t size, bool convert_to_rgba32) - { - error = 0; - if(size == 0 || in == 0) { error = 48; return; } //the given data is empty - readPngHeader(&in[0], size); if(error) return; - size_t pos = 33; //first byte of the first chunk after the header - std::vector idat; //the data from idat chunks - bool IEND = false; - info.key_defined = false; - while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer - { - if(pos + 8 >= size) { error = 30; return; } //error: size of the in buffer too small to contain next chunk - size_t chunkLength = read32bitInt(&in[pos]); pos += 4; - if(chunkLength > 2147483647) { error = 63; return; } - if(pos + chunkLength >= size) { error = 35; return; } //error: size of the in buffer too small to contain next chunk - if(in[pos + 0] == 'I' && in[pos + 1] == 'D' && in[pos + 2] == 'A' && in[pos + 3] == 'T') //IDAT chunk, containing compressed image data - { - idat.insert(idat.end(), &in[pos + 4], &in[pos + 4 + chunkLength]); - pos += (4 + chunkLength); - } - else if(in[pos + 0] == 'I' && in[pos + 1] == 'E' && in[pos + 2] == 'N' && in[pos + 3] == 'D') { pos += 4; IEND = true; } - else if(in[pos + 0] == 'P' && in[pos + 1] == 'L' && in[pos + 2] == 'T' && in[pos + 3] == 'E') //palette chunk (PLTE) - { - pos += 4; //go after the 4 letters - info.palette.resize(4 * (chunkLength / 3)); - if(info.palette.size() > (4 * 256)) { error = 38; return; } //error: palette too big - for(size_t i = 0; i < info.palette.size(); i += 4) - { - for(size_t j = 0; j < 3; j++) info.palette[i + j] = in[pos++]; //RGB - info.palette[i + 3] = 255; //alpha - } - } - else if(in[pos + 0] == 't' && in[pos + 1] == 'R' && in[pos + 2] == 'N' && in[pos + 3] == 'S') //palette transparency chunk (tRNS) - { - pos += 4; //go after the 4 letters - if(info.colorType == 3) - { - if(4 * chunkLength > info.palette.size()) { error = 39; return; } //error: more alpha values given than there are palette entries - for(size_t i = 0; i < chunkLength; i++) info.palette[4 * i + 3] = in[pos++]; - } - else if(info.colorType == 0) - { - if(chunkLength != 2) { error = 40; return; } //error: this chunk must be 2 bytes for greyscale image - info.key_defined = 1; info.key_r = info.key_g = info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; - } - else if(info.colorType == 2) - { - if(chunkLength != 6) { error = 41; return; } //error: this chunk must be 6 bytes for RGB image - info.key_defined = 1; - info.key_r = 256 * in[pos] + in[pos + 1]; pos += 2; - info.key_g = 256 * in[pos] + in[pos + 1]; pos += 2; - info.key_b = 256 * in[pos] + in[pos + 1]; pos += 2; - } - else { error = 42; return; } //error: tRNS chunk not allowed for other color models - } - else //it's not an implemented chunk type, so ignore it: skip over the data - { - if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0) - pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk - } - pos += 4; //step over CRC (which is ignored) - } - unsigned long bpp = getBpp(info); - std::vector scanlines(((info.width * (info.height * bpp + 7)) / 8) + info.height); //now the out buffer will be filled - Zlib zlib; //decompress with the Zlib decompressor - error = zlib.decompress(scanlines, idat); if(error) return; //stop if the zlib decompressor returned an error - size_t bytewidth = (bpp + 7) / 8, outlength = (info.height * info.width * bpp + 7) / 8; - out.resize(outlength); //time to fill the out buffer - unsigned char* out_ = outlength ? &out[0] : 0; //use a regular pointer to the std::vector for faster code if compiled without optimization - if(info.interlaceMethod == 0) //no interlace, just filter - { - size_t linestart = 0, linelength = (info.width * bpp + 7) / 8; //length in bytes of a scanline, excluding the filtertype byte - if(bpp >= 8) //byte per byte - for(unsigned long y = 0; y < info.height; y++) - { - unsigned long filterType = scanlines[linestart]; - const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; - unFilterScanline(&out_[linestart - y], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; - linestart += (1 + linelength); //go to start of next scanline - } - else //less than 8 bits per pixel, so fill it up bit per bit - { - std::vector templine((info.width * bpp + 7) >> 3); //only used if bpp < 8 - for(size_t y = 0, obp = 0; y < info.height; y++) - { - unsigned long filterType = scanlines[linestart]; - const unsigned char* prevline = (y == 0) ? 0 : &out_[(y - 1) * info.width * bytewidth]; - unFilterScanline(&templine[0], &scanlines[linestart + 1], prevline, bytewidth, filterType, linelength); if(error) return; - for(size_t bp = 0; bp < info.width * bpp;) setBitOfReversedStream(obp, out_, readBitFromReversedStream(bp, &templine[0])); - linestart += (1 + linelength); //go to start of next scanline - } - } - } - else //interlaceMethod is 1 (Adam7) - { - size_t passw[7] = { (info.width + 7) / 8, (info.width + 3) / 8, (info.width + 3) / 4, (info.width + 1) / 4, (info.width + 1) / 2, (info.width + 0) / 2, (info.width + 0) / 1 }; - size_t passh[7] = { (info.height + 7) / 8, (info.height + 7) / 8, (info.height + 3) / 8, (info.height + 3) / 4, (info.height + 1) / 4, (info.height + 1) / 2, (info.height + 0) / 2 }; - size_t passstart[7] = {0}; - size_t pattern[28] = {0,4,0,2,0,1,0,0,0,4,0,2,0,1,8,8,4,4,2,2,1,8,8,8,4,4,2,2}; //values for the adam7 passes - for(int i = 0; i < 6; i++) passstart[i + 1] = passstart[i] + passh[i] * ((passw[i] ? 1 : 0) + (passw[i] * bpp + 7) / 8); - std::vector scanlineo((info.width * bpp + 7) / 8), scanlinen((info.width * bpp + 7) / 8); //"old" and "new" scanline - for(int i = 0; i < 7; i++) - adam7Pass(&out_[0], &scanlinen[0], &scanlineo[0], &scanlines[passstart[i]], info.width, pattern[i], pattern[i + 7], pattern[i + 14], pattern[i + 21], passw[i], passh[i], bpp); - } - if(convert_to_rgba32 && (info.colorType != 6 || info.bitDepth != 8)) //conversion needed - { - std::vector data = out; - error = convert(out, &data[0], info, info.width, info.height); - } - } - void readPngHeader(const unsigned char* in, size_t inlength) //read the information from the header and store it in the Info - { - if(inlength < 29) { error = 27; return; } //error: the data length is smaller than the length of the header - if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { error = 28; return; } //no PNG signature - if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') { error = 29; return; } //error: it doesn't start with a IHDR chunk! - info.width = read32bitInt(&in[16]); info.height = read32bitInt(&in[20]); - info.bitDepth = in[24]; info.colorType = in[25]; - info.compressionMethod = in[26]; if(in[26] != 0) { error = 32; return; } //error: only compression method 0 is allowed in the specification - info.filterMethod = in[27]; if(in[27] != 0) { error = 33; return; } //error: only filter method 0 is allowed in the specification - info.interlaceMethod = in[28]; if(in[28] > 1) { error = 34; return; } //error: only interlace methods 0 and 1 exist in the specification - error = checkColorValidity(info.colorType, info.bitDepth); - } - void unFilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, size_t bytewidth, unsigned long filterType, size_t length) - { - switch(filterType) - { - case 0: for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; break; - case 1: - for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; - for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; - break; - case 2: - if(precon) for(size_t i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; - else for(size_t i = 0; i < length; i++) recon[i] = scanline[i]; - break; - case 3: - if(precon) - { - for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; - for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); - } - else - { - for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; - for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; - } - break; - case 4: - if(precon) - { - for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i] + paethPredictor(0, precon[i], 0); - for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]); - } - else - { - for(size_t i = 0; i < bytewidth; i++) recon[i] = scanline[i]; - for(size_t i = bytewidth; i < length; i++) recon[i] = scanline[i] + paethPredictor(recon[i - bytewidth], 0, 0); - } - break; - default: error = 36; return; //error: unexisting filter type given - } - } - void adam7Pass(unsigned char* out, unsigned char* linen, unsigned char* lineo, const unsigned char* in, unsigned long w, size_t passleft, size_t passtop, size_t spacex, size_t spacey, size_t passw, size_t passh, unsigned long bpp) - { //filter and reposition the pixels into the output when the image is Adam7 interlaced. This function can only do it after the full image is already decoded. The out buffer must have the correct allocated memory size already. - if(passw == 0) return; - size_t bytewidth = (bpp + 7) / 8, linelength = 1 + ((bpp * passw + 7) / 8); - for(unsigned long y = 0; y < passh; y++) - { - unsigned char filterType = in[y * linelength], *prevline = (y == 0) ? 0 : lineo; - unFilterScanline(linen, &in[y * linelength + 1], prevline, bytewidth, filterType, (w * bpp + 7) / 8); if(error) return; - if(bpp >= 8) for(size_t i = 0; i < passw; i++) for(size_t b = 0; b < bytewidth; b++) //b = current byte of this pixel - out[bytewidth * w * (passtop + spacey * y) + bytewidth * (passleft + spacex * i) + b] = linen[bytewidth * i + b]; - else for(size_t i = 0; i < passw; i++) - { - size_t obp = bpp * w * (passtop + spacey * y) + bpp * (passleft + spacex * i), bp = i * bpp; - for(size_t b = 0; b < bpp; b++) setBitOfReversedStream(obp, out, readBitFromReversedStream(bp, &linen[0])); - } - unsigned char* temp = linen; linen = lineo; lineo = temp; //swap the two buffer pointers "line old" and "line new" - } - } - static unsigned long readBitFromReversedStream(size_t& bitp, const unsigned char* bits) { unsigned long result = (bits[bitp >> 3] >> (7 - (bitp & 0x7))) & 1; bitp++; return result;} - static unsigned long readBitsFromReversedStream(size_t& bitp, const unsigned char* bits, unsigned long nbits) - { - unsigned long result = 0; - for(size_t i = nbits - 1; i < nbits; i--) result += ((readBitFromReversedStream(bitp, bits)) << i); - return result; - } - void setBitOfReversedStream(size_t& bitp, unsigned char* bits, unsigned long bit) { bits[bitp >> 3] |= (bit << (7 - (bitp & 0x7))); bitp++; } - unsigned long read32bitInt(const unsigned char* buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; } - int checkColorValidity(unsigned long colorType, unsigned long bd) //return type is a LodePNG error code - { - if((colorType == 2 || colorType == 4 || colorType == 6)) { if(!(bd == 8 || bd == 16)) return 37; else return 0; } - else if(colorType == 0) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; else return 0; } - else if(colorType == 3) { if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; else return 0; } - else return 31; //unexisting color type - } - unsigned long getBpp(const Info& info) - { - if(info.colorType == 2) return (3 * info.bitDepth); - else if(info.colorType >= 4) return (info.colorType - 2) * info.bitDepth; - else return info.bitDepth; - } - int convert(std::vector& out, const unsigned char* in, Info& infoIn, unsigned long w, unsigned long h) - { //converts from any color type to 32-bit. return value = LodePNG error code - size_t numpixels = w * h, bp = 0; - out.resize(numpixels * 4); - unsigned char* out_ = out.empty() ? 0 : &out[0]; //faster if compiled without optimization - if(infoIn.bitDepth == 8 && infoIn.colorType == 0) //greyscale - for(size_t i = 0; i < numpixels; i++) - { - out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[i]; - out_[4 * i + 3] = (infoIn.key_defined && in[i] == infoIn.key_r) ? 0 : 255; - } - else if(infoIn.bitDepth == 8 && infoIn.colorType == 2) //RGB color - for(size_t i = 0; i < numpixels; i++) - { - for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[3 * i + c]; - out_[4 * i + 3] = (infoIn.key_defined == 1 && in[3 * i + 0] == infoIn.key_r && in[3 * i + 1] == infoIn.key_g && in[3 * i + 2] == infoIn.key_b) ? 0 : 255; - } - else if(infoIn.bitDepth == 8 && infoIn.colorType == 3) //indexed color (palette) - for(size_t i = 0; i < numpixels; i++) - { - if(4U * in[i] >= infoIn.palette.size()) return 46; - for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * in[i] + c]; //get rgb colors from the palette - } - else if(infoIn.bitDepth == 8 && infoIn.colorType == 4) //greyscale with alpha - for(size_t i = 0; i < numpixels; i++) - { - out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i + 0]; - out_[4 * i + 3] = in[2 * i + 1]; - } - else if(infoIn.bitDepth == 8 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[4 * i + c]; //RGB with alpha - else if(infoIn.bitDepth == 16 && infoIn.colorType == 0) //greyscale - for(size_t i = 0; i < numpixels; i++) - { - out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[2 * i]; - out_[4 * i + 3] = (infoIn.key_defined && 256U * in[i] + in[i + 1] == infoIn.key_r) ? 0 : 255; - } - else if(infoIn.bitDepth == 16 && infoIn.colorType == 2) //RGB color - for(size_t i = 0; i < numpixels; i++) - { - for(size_t c = 0; c < 3; c++) out_[4 * i + c] = in[6 * i + 2 * c]; - out_[4 * i + 3] = (infoIn.key_defined && 256U*in[6*i+0]+in[6*i+1] == infoIn.key_r && 256U*in[6*i+2]+in[6*i+3] == infoIn.key_g && 256U*in[6*i+4]+in[6*i+5] == infoIn.key_b) ? 0 : 255; - } - else if(infoIn.bitDepth == 16 && infoIn.colorType == 4) //greyscale with alpha - for(size_t i = 0; i < numpixels; i++) - { - out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = in[4 * i]; //most significant byte - out_[4 * i + 3] = in[4 * i + 2]; - } - else if(infoIn.bitDepth == 16 && infoIn.colorType == 6) for(size_t i = 0; i < numpixels; i++) for(size_t c = 0; c < 4; c++) out_[4 * i + c] = in[8 * i + 2 * c]; //RGB with alpha - else if(infoIn.bitDepth < 8 && infoIn.colorType == 0) //greyscale - for(size_t i = 0; i < numpixels; i++) - { - unsigned long value = (readBitsFromReversedStream(bp, in, infoIn.bitDepth) * 255) / ((1 << infoIn.bitDepth) - 1); //scale value from 0 to 255 - out_[4 * i + 0] = out_[4 * i + 1] = out_[4 * i + 2] = (unsigned char)(value); - out_[4 * i + 3] = (infoIn.key_defined && value && ((1U << infoIn.bitDepth) - 1U) == infoIn.key_r && ((1U << infoIn.bitDepth) - 1U)) ? 0 : 255; - } - else if(infoIn.bitDepth < 8 && infoIn.colorType == 3) //palette - for(size_t i = 0; i < numpixels; i++) - { - unsigned long value = readBitsFromReversedStream(bp, in, infoIn.bitDepth); - if(4 * value >= infoIn.palette.size()) return 47; - for(size_t c = 0; c < 4; c++) out_[4 * i + c] = infoIn.palette[4 * value + c]; //get rgb colors from the palette - } - return 0; - } - unsigned char paethPredictor(short a, short b, short c) //Paeth predicter, used by PNG filter type 4 - { - short p = a + b - c, pa = p > a ? (p - a) : (a - p), pb = p > b ? (p - b) : (b - p), pc = p > c ? (p - c) : (c - p); - return (unsigned char)((pa <= pb && pa <= pc) ? a : pb <= pc ? b : c); - } - }; - PNG decoder = { }; decoder.decode(out_image, in_png, in_size, convert_to_rgba32); - image_width = decoder.info.width; image_height = decoder.info.height; - return decoder.error; + int r = 0; + unique_ptr ctx = unique_ptr(spng_ctx_new(0)); + if(r = spng_set_crc_action(ctx.get(), SPNG_CRC_USE, SPNG_CRC_USE)) { + return r; + } + + size_t limit = 1024 * 1024 * 64; + if(r = spng_set_chunk_limits(ctx.get(), limit, limit)) { + return r; + } + + if(r = spng_set_png_buffer(ctx.get(), in_png, in_size)) { + return r; + } + + struct spng_ihdr ihdr; + if(r = spng_get_ihdr(ctx.get(), &ihdr)) { + return r; + } + + image_width = ihdr.width; + image_height = ihdr.height; + + int fmt = SPNG_FMT_RGBA8; + size_t out_size = 0; + if(r = spng_decoded_image_size(ctx.get(), fmt, &out_size)) { + return r; + } + + out_image.resize(out_size); + + if(r = spng_decode_image(ctx.get(), out_image.data(), out_image.size(), fmt, 0)) { + return r; + } + + return 0; } \ No newline at end of file diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index f9b9f296..6634fcd2 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -470,6 +470,7 @@ + @@ -592,7 +593,18 @@ NotUsing - + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + @@ -641,6 +653,19 @@ + + NotUsing + Default + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + Create Create diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index 127d1317..e4abfe0b 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -226,6 +226,9 @@ Misc + + Misc + @@ -343,5 +346,6 @@ + \ No newline at end of file diff --git a/Utilities/miniz.h b/Utilities/miniz.h index a7e550f2..be6225b3 100644 --- a/Utilities/miniz.h +++ b/Utilities/miniz.h @@ -157,7 +157,6 @@ */ #pragma once -#include "stdafx.h" #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED diff --git a/Utilities/spng.c b/Utilities/spng.c new file mode 100644 index 00000000..ff256480 --- /dev/null +++ b/Utilities/spng.c @@ -0,0 +1,4778 @@ +/* SPDX-License-Identifier: (BSD-2-Clause AND libpng-2.0) */ +#define SPNG__BUILD + +#include "spng.h" + +#include +#include +#include +#include + +#define ZLIB_CONST + +#ifdef __FRAMAC__ + #define SPNG_DISABLE_OPT + #include "tests/framac_stubs.h" +#else + #ifdef SPNG_USE_MINIZ + #include + #else + #include + #endif +#endif + +#ifdef SPNG_MULTITHREADING + #include +#endif + +#define SPNG_READ_SIZE 8192 + +#define SPNG_TARGET_CLONES(x) + +#ifndef SPNG_DISABLE_OPT + + #if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) + #define SPNG_X86 + + #if defined(__x86_64__) || defined(_M_X64) + #define SPNG_X86_64 + #endif + + #elif defined(__aarch64__) || defined(_M_ARM64) || defined(__ARM_NEON) + /* #define SPNG_ARM */ /* buffer overflow for rgb8 images */ + #define SPNG_DISABLE_OPT + #else + #pragma message "disabling SIMD optimizations for unknown target" + #define SPNG_DISABLE_OPT + #endif + + #if defined(SPNG_X86_64) && defined(SPNG_ENABLE_TARGET_CLONES) + #undef SPNG_TARGET_CLONES + #define SPNG_TARGET_CLONES(x) __attribute__((target_clones(x))) + #else + #define SPNG_TARGET_CLONES(x) + #endif + + #ifndef SPNG_DISABLE_OPT + static void defilter_sub3(size_t rowbytes, unsigned char *row); + static void defilter_sub4(size_t rowbytes, unsigned char *row); + static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev); + static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev); + #endif +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable: 4244) +#endif + +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || defined(__BIG_ENDIAN__) + #define SPNG_BIG_ENDIAN +#else + #define SPNG_LITTLE_ENDIAN +#endif + +enum spng_state +{ + SPNG_STATE_INVALID = 0, + SPNG_STATE_INIT = 1, /* No PNG buffer/stream is set */ + SPNG_STATE_INPUT, /* Input PNG was set */ + SPNG_STATE_IHDR, /* IHDR was read */ + SPNG_STATE_FIRST_IDAT, /* Reached first IDAT */ + SPNG_STATE_DECODE_INIT, /* Decoder is ready for progressive reads */ + SPNG_STATE_EOI, /* Reached the last scanline/row */ + SPNG_STATE_LAST_IDAT, /* Reached last IDAT, set at end of decode_image() */ + SPNG_STATE_AFTER_IDAT, /* */ + SPNG_STATE_IEND, /* Reached IEND */ +}; + +#define SPNG_STR(x) _SPNG_STR(x) +#define _SPNG_STR(x) #x + +#define SPNG_VERSION_STRING SPNG_STR(SPNG_VERSION_MAJOR) "." \ + SPNG_STR(SPNG_VERSION_MINOR) "." \ + SPNG_STR(SPNG_VERSION_PATCH) + +#define SPNG_GET_CHUNK_BOILERPLATE(chunk) \ + if(ctx == NULL) return 1; \ + int ret = read_chunks(ctx, 0); \ + if(ret) return ret; \ + if(!ctx->stored.chunk) return SPNG_ECHUNKAVAIL; \ + if(chunk == NULL) return 1 + +#define SPNG_SET_CHUNK_BOILERPLATE(chunk) \ + if(ctx == NULL || chunk == NULL) return 1; \ + if(ctx->data == NULL) ctx->encode_only = 1; \ + int ret = read_chunks(ctx, 0); \ + if(ret) return ret + +struct spng_text2 +{ + int type; + char *keyword; + char *text; + + size_t text_length; + + uint8_t compression_flag; /* iTXt only */ + char *language_tag; /* iTXt only */ + char *translated_keyword; /* iTXt only */ + + size_t cache_usage; +}; + + +/* Packed sample iterator */ +struct spng__iter +{ + const uint8_t mask; + unsigned shift_amount; + const unsigned initial_shift, bit_depth; + const unsigned char *samples; +}; + +static const uint32_t png_u32max = 2147483647; + +static const uint32_t adam7_x_start[7] = { 0, 4, 0, 2, 0, 1, 0 }; +static const uint32_t adam7_y_start[7] = { 0, 0, 4, 0, 2, 0, 1 }; +static const uint32_t adam7_x_delta[7] = { 8, 8, 4, 4, 2, 2, 1 }; +static const uint32_t adam7_y_delta[7] = { 8, 8, 8, 4, 4, 2, 2 }; + +static const uint8_t png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + +static const uint8_t type_ihdr[4] = { 73, 72, 68, 82 }; +static const uint8_t type_plte[4] = { 80, 76, 84, 69 }; +static const uint8_t type_idat[4] = { 73, 68, 65, 84 }; +static const uint8_t type_iend[4] = { 73, 69, 78, 68 }; + +static const uint8_t type_trns[4] = { 116, 82, 78, 83 }; +static const uint8_t type_chrm[4] = { 99, 72, 82, 77 }; +static const uint8_t type_gama[4] = { 103, 65, 77, 65 }; +static const uint8_t type_iccp[4] = { 105, 67, 67, 80 }; +static const uint8_t type_sbit[4] = { 115, 66, 73, 84 }; +static const uint8_t type_srgb[4] = { 115, 82, 71, 66 }; +static const uint8_t type_text[4] = { 116, 69, 88, 116 }; +static const uint8_t type_ztxt[4] = { 122, 84, 88, 116 }; +static const uint8_t type_itxt[4] = { 105, 84, 88, 116 }; +static const uint8_t type_bkgd[4] = { 98, 75, 71, 68 }; +static const uint8_t type_hist[4] = { 104, 73, 83, 84 }; +static const uint8_t type_phys[4] = { 112, 72, 89, 115 }; +static const uint8_t type_splt[4] = { 115, 80, 76, 84 }; +static const uint8_t type_time[4] = { 116, 73, 77, 69 }; + +static const uint8_t type_offs[4] = { 111, 70, 70, 115 }; +static const uint8_t type_exif[4] = { 101, 88, 73, 102 }; + +static inline void *spng__malloc(spng_ctx *ctx, size_t size) +{ + return ctx->alloc.malloc_fn(size); +} + +static inline void *spng__calloc(spng_ctx *ctx, size_t nmemb, size_t size) +{ + return ctx->alloc.calloc_fn(nmemb, size); +} + +static inline void *spng__realloc(spng_ctx *ctx, void *ptr, size_t size) +{ + return ctx->alloc.realloc_fn(ptr, size); +} + +static inline void spng__free(spng_ctx *ctx, void *ptr) +{ + ctx->alloc.free_fn(ptr); +} + +#if defined(SPNG_USE_MINIZ) +static void *spng__zalloc(void *opaque, size_t items, size_t size) +#else +static void *spng__zalloc(void *opaque, uInt items, uInt size) +#endif +{ + spng_ctx *ctx = opaque; + + if(size > SIZE_MAX / items) return NULL; + + size_t len = (size_t)items * size; + + return spng__malloc(ctx, len); +} + +static void spng__zfree(void *opqaue, void *ptr) +{ + spng_ctx *ctx = opqaue; + spng__free(ctx, ptr); +} + +static inline uint16_t read_u16(const void *_data) +{ + const unsigned char *data = _data; + + return (data[0] & 0xFFU) << 8 | (data[1] & 0xFFU); +} + +static inline uint32_t read_u32(const void *_data) +{ + const unsigned char *data = _data; + + return (data[0] & 0xFFUL) << 24 | (data[1] & 0xFFUL) << 16 | + (data[2] & 0xFFUL) << 8 | (data[3] & 0xFFUL); +} + +static inline int32_t read_s32(const void *_data) +{ + const unsigned char *data = _data; + + int32_t ret; + uint32_t val = (data[0] & 0xFFUL) << 24 | (data[1] & 0xFFUL) << 16 | + (data[2] & 0xFFUL) << 8 | (data[3] & 0xFFUL); + + memcpy(&ret, &val, 4); + + return ret; +} + +/* Returns an iterator for 1,2,4,8-bit samples */ +static struct spng__iter spng__iter_init(unsigned bit_depth, const unsigned char *samples) +{ + struct spng__iter iter = + { + .mask = (uint32_t)(1 << bit_depth) - 1, + .shift_amount = 8 - bit_depth, + .initial_shift = 8 - bit_depth, + .bit_depth = bit_depth, + .samples = samples + }; + + return iter; +} + +/* Returns the current sample unpacked, iterates to the next one */ +static inline uint8_t get_sample(struct spng__iter *iter) +{ + uint8_t x = (iter->samples[0] >> iter->shift_amount) & iter->mask; + + iter->shift_amount -= iter->bit_depth; + + if(iter->shift_amount > 7) + { + iter->shift_amount = iter->initial_shift; + iter->samples++; + } + + return x; +} + +static void u16_row_to_host(void *row, size_t size) +{ + uint16_t *px = row; + size_t i, n = size / 2; + for(i=0; i < n; i++) + { + px[i] = read_u16(&px[i]); + } +} + +static void rgb8_row_to_rgba8(const unsigned char *row, unsigned char *out, uint32_t n) +{ + uint32_t i; + for(i=0; i < n; i++) + { + memcpy(out + i * 4, row + i * 3, 3); + out[i*4+3] = 255; + } +} + +/* Calculate scanline width in bits, round up to the nearest byte */ +static int calculate_scanline_width(struct spng_ctx *ctx, uint32_t width, size_t *scanline_width) +{ + if(!width) return SPNG_EINTERNAL; + + size_t res = ctx->channels * ctx->ihdr.bit_depth; + + if(res > SIZE_MAX / width) return SPNG_EOVERFLOW; + res = res * width; + + res += 15; /* Filter byte + 7 for rounding */ + + if(res < 15) return SPNG_EOVERFLOW; + + res /= 8; + + if(res > UINT32_MAX) return SPNG_EOVERFLOW; + + *scanline_width = res; + + return 0; +} + +static int calculate_subimages(struct spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + struct spng_ihdr *ihdr = &ctx->ihdr; + struct spng_subimage *sub = ctx->subimage; + + if(ihdr->interlace_method == 1) + { + sub[0].width = (ihdr->width + 7) >> 3; + sub[0].height = (ihdr->height + 7) >> 3; + sub[1].width = (ihdr->width + 3) >> 3; + sub[1].height = (ihdr->height + 7) >> 3; + sub[2].width = (ihdr->width + 3) >> 2; + sub[2].height = (ihdr->height + 3) >> 3; + sub[3].width = (ihdr->width + 1) >> 2; + sub[3].height = (ihdr->height + 3) >> 2; + sub[4].width = (ihdr->width + 1) >> 1; + sub[4].height = (ihdr->height + 1) >> 2; + sub[5].width = ihdr->width >> 1; + sub[5].height = (ihdr->height + 1) >> 1; + sub[6].width = ihdr->width; + sub[6].height = ihdr->height >> 1; + } + else + { + sub[0].width = ihdr->width; + sub[0].height = ihdr->height; + } + + int i; + for(i=0; i < 7; i++) + { + if(sub[i].width == 0 || sub[i].height == 0) continue; + + int ret = calculate_scanline_width(ctx, sub[i].width, &sub[i].scanline_width); + if(ret) return ret; + + if(sub[ctx->widest_pass].scanline_width < sub[i].scanline_width) ctx->widest_pass = i; + + ctx->last_pass = i; + } + + return 0; +} + + +static int increase_cache_usage(spng_ctx *ctx, size_t bytes) +{ + if(ctx == NULL || !bytes) return SPNG_EINTERNAL; + + size_t new_usage = ctx->chunk_cache_usage + bytes; + + if(new_usage < ctx->chunk_cache_usage) return SPNG_EOVERFLOW; + + if(new_usage > ctx->chunk_cache_limit) return SPNG_ECHUNK_LIMITS; + + ctx->chunk_cache_usage = new_usage; + + return 0; +} + +static int decrease_cache_usage(spng_ctx *ctx, size_t usage) +{ + if(ctx == NULL || !usage) return SPNG_EINTERNAL; + if(usage > ctx->chunk_cache_usage) return SPNG_EINTERNAL; + + ctx->chunk_cache_usage -= usage; + + return 0; +} + +static int is_critical_chunk(struct spng_chunk *chunk) +{ + if(chunk == NULL) return 0; + if((chunk->type[0] & (1 << 5)) == 0) return 1; + + return 0; +} + +static inline int read_data(spng_ctx *ctx, size_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!bytes) return 0; + + if(ctx->streaming && (bytes > SPNG_READ_SIZE)) return SPNG_EINTERNAL; + + int ret = ctx->read_fn(ctx, ctx->read_user_ptr, ctx->stream_buf, bytes); + + if(ret) + { + if(ret > 0 || ret < SPNG_IO_ERROR) ret = SPNG_IO_ERROR; + + return ret; + } + + ctx->bytes_read += bytes; + if(ctx->bytes_read < bytes) return SPNG_EOVERFLOW; + + return 0; +} + +/* Read and check the current chunk's crc, + returns -SPNG_CRC_DISCARD if the chunk should be discarded */ +static inline int read_and_check_crc(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + int ret; + ret = read_data(ctx, 4); + if(ret) return ret; + + ctx->current_chunk.crc = read_u32(ctx->data); + + if(ctx->skip_crc) return 0; + + if(ctx->cur_actual_crc != ctx->current_chunk.crc) + { + if(is_critical_chunk(&ctx->current_chunk)) + { + if(ctx->crc_action_critical == SPNG_CRC_USE) return 0; + } + else + { + if(ctx->crc_action_ancillary == SPNG_CRC_USE) return 0; + if(ctx->crc_action_ancillary == SPNG_CRC_DISCARD) return -SPNG_CRC_DISCARD; + } + + return SPNG_ECHUNK_CRC; + } + + return 0; +} + +/* Read and validate the current chunk's crc and the next chunk header */ +static inline int read_header(spng_ctx *ctx) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + + int ret; + struct spng_chunk chunk = { 0 }; + + ret = read_and_check_crc(ctx); + if(ret) + { + if(ret == -SPNG_CRC_DISCARD) + { + ctx->discard = 1; + } + else return ret; + } + + ret = read_data(ctx, 8); + if(ret) return ret; + + chunk.offset = ctx->bytes_read - 8; + + chunk.length = read_u32(ctx->data); + + memcpy(&chunk.type, ctx->data + 4, 4); + + if(chunk.length > png_u32max) return SPNG_ECHUNK_STDLEN; + + ctx->cur_chunk_bytes_left = chunk.length; + + if(is_critical_chunk(&chunk) && ctx->crc_action_critical == SPNG_CRC_USE) ctx->skip_crc = 1; + else if(ctx->crc_action_ancillary == SPNG_CRC_USE) ctx->skip_crc = 1; + else ctx->skip_crc = 0; + + if(!ctx->skip_crc) + { + ctx->cur_actual_crc = crc32(0, NULL, 0); + ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, chunk.type, 4); + } + + ctx->current_chunk = chunk; + + return 0; +} + +/* Read chunk bytes and update crc */ +static int read_chunk_bytes(spng_ctx *ctx, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->cur_chunk_bytes_left || !bytes) return SPNG_EINTERNAL; + if(bytes > ctx->cur_chunk_bytes_left) return SPNG_EINTERNAL; /* XXX: more specific error? */ + + int ret; + + ret = read_data(ctx, bytes); + if(ret) return ret; + + if(!ctx->skip_crc) ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, ctx->data, bytes); + + ctx->cur_chunk_bytes_left -= bytes; + + return ret; +} + +/* read_chunk_bytes() + read_data() with custom output buffer */ +static int read_chunk_bytes2(spng_ctx *ctx, void *out, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->cur_chunk_bytes_left || !bytes) return SPNG_EINTERNAL; + if(bytes > ctx->cur_chunk_bytes_left) return SPNG_EINTERNAL; /* XXX: more specific error? */ + + int ret; + uint32_t len = bytes; + + if(ctx->streaming && len > SPNG_READ_SIZE) len = SPNG_READ_SIZE; + + while(bytes) + { + if(len > bytes) len = bytes; + + ret = ctx->read_fn(ctx, ctx->read_user_ptr, out, len); + if(ret) return ret; + + if(!ctx->streaming) memcpy(out, ctx->data, len); + + ctx->bytes_read += len; + if(ctx->bytes_read < len) return SPNG_EOVERFLOW; + + if(!ctx->skip_crc) ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, out, len); + + ctx->cur_chunk_bytes_left -= len; + + out = (char*)out + len; + bytes -= len; + len = SPNG_READ_SIZE; + } + + return 0; +} + +static int discard_chunk_bytes(spng_ctx *ctx, uint32_t bytes) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!bytes) return 0; + + int ret; + + if(ctx->streaming) /* Do small, consecutive reads */ + { + while(bytes) + { + uint32_t len = SPNG_READ_SIZE; + + if(len > bytes) len = bytes; + + ret = read_chunk_bytes(ctx, len); + if(ret) return ret; + + bytes -= len; + } + } + else + { + ret = read_chunk_bytes(ctx, bytes); + if(ret) return ret; + } + + return 0; +} + +static int spng__inflate_init(spng_ctx *ctx) +{ + if(ctx->zstream.state) inflateEnd(&ctx->zstream); + + ctx->zstream.zalloc = spng__zalloc; + ctx->zstream.zfree = spng__zfree; + ctx->zstream.opaque = ctx; + + if(inflateInit(&ctx->zstream) != Z_OK) return SPNG_EZLIB_INIT; + +#if ZLIB_VERNUM >= 0x1290 && !defined(SPNG_USE_MINIZ) + + int validate = 1; + + if(ctx->flags & SPNG_CTX_IGNORE_ADLER32) validate = 0; + + if(is_critical_chunk(&ctx->current_chunk)) + { + if(ctx->crc_action_critical == SPNG_CRC_USE) validate = 0; + } + else /* ancillary */ + { + if(ctx->crc_action_ancillary == SPNG_CRC_USE) validate = 0; + } + + if(inflateValidate(&ctx->zstream, validate)) return SPNG_EZLIB_INIT; + +#else /* This requires zlib >= 1.2.11 */ + #pragma message ("inflateValidate() not available, SPNG_CTX_IGNORE_ADLER32 will be ignored") +#endif + + return 0; +} + +/* Inflate a zlib stream starting with start_buf if non-NULL, + continuing from the datastream till an end marker, + allocating and writing the inflated stream to *out, + leaving "extra" bytes at the end, final buffer length is *len. + + Takes into account the chunk size and cache limits. +*/ +static int spng__inflate_stream(spng_ctx *ctx, char **out, size_t *len, size_t extra, const void *start_buf, size_t start_len) +{ + int ret = spng__inflate_init(ctx); + if(ret) return ret; + + size_t max = ctx->chunk_cache_limit - ctx->chunk_cache_usage; + + if(ctx->max_chunk_size < max) max = ctx->max_chunk_size; + + if(extra > max) return SPNG_ECHUNK_LIMITS; + max -= extra; + + uint32_t read_size; + size_t size = 8 * 1024; + void *t, *buf = spng__malloc(ctx, size); + + if(buf == NULL) return SPNG_EMEM; + + z_stream *stream = &ctx->zstream; + + if(start_buf != NULL && start_len) + { + stream->avail_in = (uInt)start_len; + stream->next_in = start_buf; + } + else + { + stream->avail_in = 0; + stream->next_in = NULL; + } + + stream->avail_out = (uInt)size; + stream->next_out = buf; + + while(ret != Z_STREAM_END) + { + ret = inflate(stream, 0); + + if(ret == Z_STREAM_END) break; + + if(ret != Z_OK && ret != Z_BUF_ERROR) + { + ret = SPNG_EZLIB; + goto err; + } + + if(!stream->avail_out) /* Resize buffer */ + { + /* overflow or reached chunk/cache limit */ + if( (2 > SIZE_MAX / size) || (size > max / 2) ) + { + ret = SPNG_ECHUNK_LIMITS; + goto err; + } + + size *= 2; + + t = spng__realloc(ctx, buf, size); + if(t == NULL) goto mem; + + buf = t; + + stream->avail_out = (uInt)size / 2; + stream->next_out = (unsigned char*)buf + size / 2; + } + else if(!stream->avail_in) /* Read more chunk bytes */ + { + read_size = ctx->cur_chunk_bytes_left; + if(ctx->streaming && read_size > SPNG_READ_SIZE) read_size = SPNG_READ_SIZE; + + ret = read_chunk_bytes(ctx, read_size); + + if(ret) + { + if(!read_size) ret = SPNG_EZLIB; + + goto err; + } + + stream->avail_in = read_size; + stream->next_in = ctx->data; + } + } + + size = stream->total_out; + + if(!size) + { + ret = SPNG_EZLIB; + goto err; + } + + size += extra; + if(size < extra) goto mem; + + t = spng__realloc(ctx, buf, size); + if(t == NULL) goto mem; + + buf = t; + + (void)increase_cache_usage(ctx, size); + + *out = buf; + *len = size; + + return 0; + +mem: + ret = SPNG_EMEM; +err: + spng__free(ctx, buf); + return ret; +} + +/* Read at least one byte from the IDAT stream */ +static int read_idat_bytes(spng_ctx *ctx, uint32_t *bytes_read) +{ + if(ctx == NULL || bytes_read == NULL) return SPNG_EINTERNAL; + if(memcmp(ctx->current_chunk.type, type_idat, 4)) return SPNG_EIDAT_TOO_SHORT; + + int ret; + uint32_t len; + + while(!ctx->cur_chunk_bytes_left) + { + ret = read_header(ctx); + if(ret) return ret; + + if(memcmp(ctx->current_chunk.type, type_idat, 4)) return SPNG_EIDAT_TOO_SHORT; + } + + if(ctx->streaming) + {/* TODO: estimate bytes to read for progressive reads */ + len = SPNG_READ_SIZE; + if(len > ctx->cur_chunk_bytes_left) len = ctx->cur_chunk_bytes_left; + } + else len = ctx->current_chunk.length; + + ret = read_chunk_bytes(ctx, len); + + *bytes_read = len; + + return ret; +} + +static int read_scanline_bytes(spng_ctx *ctx, unsigned char *dest, size_t len) +{ + if(ctx == NULL || dest == NULL) return SPNG_EINTERNAL; + + int ret = Z_OK; + uint32_t bytes_read; + + z_stream *zstream = &ctx->zstream; + + zstream->avail_out = (uInt)len; + zstream->next_out = dest; + + while(zstream->avail_out != 0) + { + ret = inflate(&ctx->zstream, 0); + + if(ret == Z_OK) continue; + + if(ret == Z_STREAM_END) /* Reached an end-marker */ + { + if(zstream->avail_out != 0) return SPNG_EIDAT_TOO_SHORT; + } + else if(ret == Z_BUF_ERROR) /* Read more IDAT bytes */ + { + ret = read_idat_bytes(ctx, &bytes_read); + if(ret) return ret; + + zstream->avail_in = bytes_read; + zstream->next_in = ctx->data; + } + else return SPNG_EIDAT_STREAM; + } + + return 0; +} + +static uint8_t paeth(uint8_t a, uint8_t b, uint8_t c) +{ + int16_t p = (int16_t)a + (int16_t)b - (int16_t)c; + int16_t pa = abs(p - (int16_t)a); + int16_t pb = abs(p - (int16_t)b); + int16_t pc = abs(p - (int16_t)c); + + if(pa <= pb && pa <= pc) return a; + else if(pb <= pc) return b; + + return c; +} + +SPNG_TARGET_CLONES("default,avx2") +static void defilter_up(size_t bytes, unsigned char *row, const unsigned char *prev) +{ + size_t i; + for(i=0; i < bytes; i++) + { + row[i] += prev[i]; + } +} + +/* Defilter *scanline in-place. + *prev_scanline and *scanline should point to the first pixel, + scanline_width is the width of the scanline including the filter byte. +*/ +static int defilter_scanline(const unsigned char *prev_scanline, unsigned char *scanline, + size_t scanline_width, unsigned bytes_per_pixel, unsigned filter) +{ + if(prev_scanline == NULL || scanline == NULL || !scanline_width) return SPNG_EINTERNAL; + + size_t i; + scanline_width--; + + if(filter == 0) return 0; + +#ifndef SPNG_DISABLE_OPT + if(filter == SPNG_FILTER_UP) goto no_opt; + + if(bytes_per_pixel == 4) + { + if(filter == SPNG_FILTER_SUB) + defilter_sub4(scanline_width, scanline); + else if(filter == SPNG_FILTER_AVERAGE) + defilter_avg4(scanline_width, scanline, prev_scanline); + else if(filter == SPNG_FILTER_PAETH) + defilter_paeth4(scanline_width, scanline, prev_scanline); + else return SPNG_EFILTER; + + return 0; + } + else if(bytes_per_pixel == 3) + { + if(filter == SPNG_FILTER_SUB) + defilter_sub3(scanline_width, scanline); + else if(filter == SPNG_FILTER_AVERAGE) + defilter_avg3(scanline_width, scanline, prev_scanline); + else if(filter == SPNG_FILTER_PAETH) + defilter_paeth3(scanline_width, scanline, prev_scanline); + else return SPNG_EFILTER; + + return 0; + } +no_opt: +#endif + + if(filter == SPNG_FILTER_UP) + { + defilter_up(scanline_width, scanline, prev_scanline); + return 0; + } + + for(i=0; i < scanline_width; i++) + { + uint8_t x, a, b, c; + + if(i >= bytes_per_pixel) + { + a = scanline[i - bytes_per_pixel]; + b = prev_scanline[i]; + c = prev_scanline[i - bytes_per_pixel]; + } + else /* First pixel in row */ + { + a = 0; + b = prev_scanline[i]; + c = 0; + } + + x = scanline[i]; + + switch(filter) + { + case SPNG_FILTER_SUB: + { + x = x + a; + break; + } + case SPNG_FILTER_AVERAGE: + { + uint16_t avg = (a + b) / 2; + x = x + avg; + break; + } + case SPNG_FILTER_PAETH: + { + x = x + paeth(a,b,c); + break; + } + } + + scanline[i] = x; + } + + return 0; +} + +/* Scale "sbits" significant bits in "sample" from "bit_depth" to "target" + + "bit_depth" must be a valid PNG depth + "sbits" must be less than or equal to "bit_depth" + "target" must be between 1 and 16 +*/ +static uint16_t sample_to_target(uint16_t sample, unsigned bit_depth, unsigned sbits, unsigned target) +{ + if(bit_depth == sbits) + { + if(target == sbits) return sample; /* No scaling */ + }/* bit_depth > sbits */ + else sample = sample >> (bit_depth - sbits); /* Shift significant bits to bottom */ + + /* Downscale */ + if(target < sbits) return sample >> (sbits - target); + + /* Upscale using left bit replication */ + int8_t shift_amount = target - sbits; + uint16_t sample_bits = sample; + sample = 0; + + while(shift_amount >= 0) + { + sample = sample | (sample_bits << shift_amount); + shift_amount -= sbits; + } + + int8_t partial = shift_amount + (int8_t)sbits; + + if(partial != 0) sample = sample | (sample_bits >> abs(shift_amount)); + + return sample; +} + +static inline void gamma_correct_row(unsigned char *row, uint32_t pixels, int fmt, const uint16_t *gamma_lut) +{ + uint32_t i; + + if(fmt == SPNG_FMT_RGBA8) + { + unsigned char *px; + for(i=0; i < pixels; i++) + { + px = row + i * 4; + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + for(i=0; i < pixels; i++) + { + uint16_t px[4]; + memcpy(px, row + i * 8, 8); + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + + memcpy(row + i * 8, px, 8); + } + } + else if(fmt == SPNG_FMT_RGB8) + { + unsigned char *px; + for(i=0; i < pixels; i++) + { + px = row + i * 3; + + px[0] = gamma_lut[px[0]]; + px[1] = gamma_lut[px[1]]; + px[2] = gamma_lut[px[2]]; + } + } +} + +/* Apply transparency to output row */ +static inline void trns_row(unsigned char *row, + const unsigned char *scanline, + const unsigned char *trns, + unsigned scanline_stride, + struct spng_ihdr *ihdr, + uint32_t pixels, + int fmt) +{ + uint32_t i; + unsigned row_stride; + unsigned depth = ihdr->bit_depth; + + if(fmt == SPNG_FMT_RGBA8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) return; /* already applied in the decoding loop */ + + row_stride = 4; + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) row[3] = 0; + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) return; /* already applied in the decoding loop */ + + row_stride = 8; + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) memset(row + 6, 0, 2); + } + } + else if(fmt == SPNG_FMT_GA8) + { + row_stride = 2; + + if(depth == 16) + { + for(i=0; i < pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, scanline_stride)) memset(row + 1, 0, 1); + } + } + else /* depth <= 8 */ + { + struct spng__iter iter = spng__iter_init(depth, scanline); + + for(i=0; i < pixels; i++, row+=row_stride) + { + if(trns[0] == get_sample(&iter)) row[1] = 0; + } + } + } + else if(fmt == SPNG_FMT_GA16) + { + row_stride = 4; + + if(depth == 16) + { + for(i=0; i< pixels; i++, scanline+=scanline_stride, row+=row_stride) + { + if(!memcmp(scanline, trns, 2)) memset(row + 2, 0, 2); + } + } + else + { + struct spng__iter iter = spng__iter_init(depth, scanline); + + for(i=0; i< pixels; i++, row+=row_stride) + { + if(trns[0] == get_sample(&iter)) memset(row + 2, 0, 2); + } + } + } + else return; +} + +static inline void scale_row(unsigned char *row, uint32_t pixels, int fmt, unsigned depth, const struct spng_sbit *sbit) +{ + uint32_t i; + + if(fmt == SPNG_FMT_RGBA8) + { + unsigned char px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 4, 4); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 8); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 8); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 8); + px[3] = sample_to_target(px[3], depth, sbit->alpha_bits, 8); + + memcpy(row + i * 4, px, 4); + } + } + else if(fmt == SPNG_FMT_RGBA16) + { + uint16_t px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 8, 8); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 16); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 16); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 16); + px[3] = sample_to_target(px[3], depth, sbit->alpha_bits, 16); + + memcpy(row + i * 8, px, 8); + } + } + else if(fmt == SPNG_FMT_RGB8) + { + unsigned char px[4]; + for(i=0; i < pixels; i++) + { + memcpy(px, row + i * 3, 3); + + px[0] = sample_to_target(px[0], depth, sbit->red_bits, 8); + px[1] = sample_to_target(px[1], depth, sbit->green_bits, 8); + px[2] = sample_to_target(px[2], depth, sbit->blue_bits, 8); + + memcpy(row + i * 3, px, 3); + } + } + else if(fmt == SPNG_FMT_G8) + { + for(i=0; i < pixels; i++) + { + row[i] = sample_to_target(row[i], depth, sbit->grayscale_bits, 8); + } + } + else if(fmt == SPNG_FMT_GA8) + { + for(i=0; i < pixels; i++) + { + row[i*2] = sample_to_target(row[i*2], depth, sbit->grayscale_bits, 8); + } + } +} + +/* Expand to *row using 8-bit palette indices from *scanline */ +void expand_row(unsigned char *row, const unsigned char *scanline, const struct spng_plte_entry16 *plte, uint32_t width, int fmt) +{ + uint32_t i; + unsigned char *px; + unsigned char entry; + if(fmt == SPNG_FMT_RGBA8) + { + for(i=0; i < width; i++) + { + px = row + i * 4; + entry = scanline[i]; + px[0] = plte[entry].red; + px[1] = plte[entry].green; + px[2] = plte[entry].blue; + px[3] = plte[entry].alpha; + } + } + else if(fmt == SPNG_FMT_RGB8) + { + for(i=0; i < width; i++) + { + px = row + i * 3; + entry = scanline[i]; + px[0] = plte[entry].red; + px[1] = plte[entry].green; + px[2] = plte[entry].blue; + } + } +} + +/* Unpack 1/2/4/8-bit samples to G8/GA8/GA16 or G16 -> GA16 */ +static void unpack_scanline(unsigned char *out, const unsigned char *scanline, uint32_t width, unsigned bit_depth, int fmt) +{ + struct spng__iter iter = spng__iter_init(bit_depth, scanline); + uint32_t i; + uint16_t sample, alpha = 65535; + + + if(fmt == SPNG_FMT_GA8) goto ga8; + else if(fmt == SPNG_FMT_GA16) goto ga16; + + /* 1/2/4-bit -> 8-bit */ + for(i=0; i < width; i++) out[i] = get_sample(&iter); + + return; + +ga8: + /* 1/2/4/8-bit -> GA8 */ + for(i=0; i < width; i++) + { + out[i*2] = get_sample(&iter); + out[i*2 + 1] = 255; + } + + return; + +ga16: + + /* 16 -> GA16 */ + if(bit_depth == 16) + { + for(i=0; i < width; i++) + { + memcpy(out + i * 4, scanline + i * 2, 2); + memcpy(out + i * 4 + 2, &alpha, 2); + } + return; + } + + /* 1/2/4/8-bit -> GA16 */ + for(i=0; i < width; i++) + { + sample = get_sample(&iter); + memcpy(out + i * 4, &sample, 2); + memcpy(out + i * 4 + 2, &alpha, 2); + } +} + +static int check_ihdr(const struct spng_ihdr *ihdr, uint32_t max_width, uint32_t max_height) +{ + if(ihdr->width > png_u32max || ihdr->width > max_width || !ihdr->width) return SPNG_EWIDTH; + if(ihdr->height > png_u32max || ihdr->height > max_height || !ihdr->height) return SPNG_EHEIGHT; + + switch(ihdr->color_type) + { + case SPNG_COLOR_TYPE_GRAYSCALE: + { + if( !(ihdr->bit_depth == 1 || ihdr->bit_depth == 2 || + ihdr->bit_depth == 4 || ihdr->bit_depth == 8 || + ihdr->bit_depth == 16) ) + return SPNG_EBIT_DEPTH; + + break; + } + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + { + if( !(ihdr->bit_depth == 8 || ihdr->bit_depth == 16) ) + return SPNG_EBIT_DEPTH; + + break; + } + case SPNG_COLOR_TYPE_INDEXED: + { + if( !(ihdr->bit_depth == 1 || ihdr->bit_depth == 2 || + ihdr->bit_depth == 4 || ihdr->bit_depth == 8) ) + return SPNG_EBIT_DEPTH; + + break; + } + default: return SPNG_ECOLOR_TYPE; + } + + if(ihdr->compression_method) return SPNG_ECOMPRESSION_METHOD; + if(ihdr->filter_method) return SPNG_EFILTER_METHOD; + + if(ihdr->interlace_method > 1) return SPNG_EINTERLACE_METHOD; + + return 0; +} + +static int check_plte(const struct spng_plte *plte, const struct spng_ihdr *ihdr) +{ + if(plte == NULL || ihdr == NULL) return 1; + + if(plte->n_entries == 0) return 1; + if(plte->n_entries > 256) return 1; + + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) + { + if(plte->n_entries > (1U << ihdr->bit_depth)) return 1; + } + + return 0; +} + +static int check_sbit(const struct spng_sbit *sbit, const struct spng_ihdr *ihdr) +{ + if(sbit == NULL || ihdr == NULL) return 1; + + if(ihdr->color_type == 0) + { + if(sbit->grayscale_bits == 0) return SPNG_ESBIT; + if(sbit->grayscale_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 2 || ihdr->color_type == 3) + { + if(sbit->red_bits == 0) return SPNG_ESBIT; + if(sbit->green_bits == 0) return SPNG_ESBIT; + if(sbit->blue_bits == 0) return SPNG_ESBIT; + + uint8_t bit_depth; + if(ihdr->color_type == 3) bit_depth = 8; + else bit_depth = ihdr->bit_depth; + + if(sbit->red_bits > bit_depth) return SPNG_ESBIT; + if(sbit->green_bits > bit_depth) return SPNG_ESBIT; + if(sbit->blue_bits > bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 4) + { + if(sbit->grayscale_bits == 0) return SPNG_ESBIT; + if(sbit->alpha_bits == 0) return SPNG_ESBIT; + + if(sbit->grayscale_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->alpha_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + else if(ihdr->color_type == 6) + { + if(sbit->red_bits == 0) return SPNG_ESBIT; + if(sbit->green_bits == 0) return SPNG_ESBIT; + if(sbit->blue_bits == 0) return SPNG_ESBIT; + if(sbit->alpha_bits == 0) return SPNG_ESBIT; + + if(sbit->red_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->green_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->blue_bits > ihdr->bit_depth) return SPNG_ESBIT; + if(sbit->alpha_bits > ihdr->bit_depth) return SPNG_ESBIT; + } + + return 0; +} + +static int check_chrm_int(const struct spng_chrm_int *chrm_int) +{ + if(chrm_int == NULL) return 1; + + if(chrm_int->white_point_x > png_u32max || + chrm_int->white_point_y > png_u32max || + chrm_int->red_x > png_u32max || + chrm_int->red_y > png_u32max || + chrm_int->green_x > png_u32max || + chrm_int->green_y > png_u32max || + chrm_int->blue_x > png_u32max || + chrm_int->blue_y > png_u32max) return SPNG_ECHRM; + + return 0; +} + +static int check_phys(const struct spng_phys *phys) +{ + if(phys == NULL) return 1; + + if(phys->unit_specifier > 1) return SPNG_EPHYS; + + if(phys->ppu_x > png_u32max) return SPNG_EPHYS; + if(phys->ppu_y > png_u32max) return SPNG_EPHYS; + + return 0; +} + +static int check_time(const struct spng_time *time) +{ + if(time == NULL) return 1; + + if(time->month == 0 || time->month > 12) return 1; + if(time->day == 0 || time->day > 31) return 1; + if(time->hour > 23) return 1; + if(time->minute > 59) return 1; + if(time->second > 60) return 1; + + return 0; +} + +static int check_offs(const struct spng_offs *offs) +{ + if(offs == NULL) return 1; + + if(offs->unit_specifier > 1) return 1; + + return 0; +} + +static int check_exif(const struct spng_exif *exif) +{ + if(exif == NULL) return 1; + if(exif->data == NULL) return 1; + + if(exif->length < 4) return SPNG_ECHUNK_SIZE; + if(exif->length > png_u32max) return SPNG_ECHUNK_STDLEN; + + const uint8_t exif_le[4] = { 73, 73, 42, 0 }; + const uint8_t exif_be[4] = { 77, 77, 0, 42 }; + + if(memcmp(exif->data, exif_le, 4) && memcmp(exif->data, exif_be, 4)) return 1; + + return 0; +} + +/* Validate PNG keyword */ +static int check_png_keyword(const char *str) +{ + if(str == NULL) return 1; + char len = strlen(str); + const char *end = str + len; + + if(!len) return 1; + if(str[0] == ' ') return 1; /* Leading space */ + if(end[-1] == ' ') return 1; /* Trailing space */ + if(strstr(str, " ") != NULL) return 1; /* Consecutive spaces */ + + uint8_t c; + while(str != end) + { + memcpy(&c, str, 1); + + if( (c >= 32 && c <= 126) || (c >= 161) ) str++; + else return 1; /* Invalid character */ + } + + return 0; +} + +/* Validate PNG text *str up to 'len' bytes */ +static int check_png_text(const char *str, size_t len) +{/* XXX: are consecutive newlines permitted? */ + if(str == NULL || len == 0) return 1; + + uint8_t c; + size_t i = 0; + while(i < len) + { + memcpy(&c, str + i, 1); + + if( (c >= 32 && c <= 126) || (c >= 161) || c == 10) i++; + else return 1; /* Invalid character */ + } + + return 0; +} + +/* Returns non-zero for standard chunks which are stored without allocating memory */ +static int is_small_chunk(uint8_t type[4]) +{ + if(!memcmp(type, type_plte, 4)) return 1; + else if(!memcmp(type, type_chrm, 4)) return 1; + else if(!memcmp(type, type_gama, 4)) return 1; + else if(!memcmp(type, type_sbit, 4)) return 1; + else if(!memcmp(type, type_srgb, 4)) return 1; + else if(!memcmp(type, type_bkgd, 4)) return 1; + else if(!memcmp(type, type_trns, 4)) return 1; + else if(!memcmp(type, type_hist, 4)) return 1; + else if(!memcmp(type, type_phys, 4)) return 1; + else if(!memcmp(type, type_time, 4)) return 1; + else if(!memcmp(type, type_offs, 4)) return 1; + else return 0; +} + +static int read_ihdr(spng_ctx *ctx) +{ + int ret; + struct spng_chunk chunk; + const unsigned char *data; + + chunk.offset = 8; + chunk.length = 13; + size_t sizeof_sig_ihdr = 29; + + ret = read_data(ctx, sizeof_sig_ihdr); + if(ret) return ret; + + data = ctx->data; + + if(memcmp(data, png_signature, sizeof(png_signature))) return SPNG_ESIGNATURE; + + chunk.length = read_u32(data + 8); + memcpy(&chunk.type, data + 12, 4); + + if(chunk.length != 13) return SPNG_EIHDR_SIZE; + if(memcmp(chunk.type, type_ihdr, 4)) return SPNG_ENOIHDR; + + ctx->cur_actual_crc = crc32(0, NULL, 0); + ctx->cur_actual_crc = crc32(ctx->cur_actual_crc, data + 12, 17); + + ctx->ihdr.width = read_u32(data + 16); + ctx->ihdr.height = read_u32(data + 20); + ctx->ihdr.bit_depth = data[24]; + ctx->ihdr.color_type = data[25]; + ctx->ihdr.compression_method = data[26]; + ctx->ihdr.filter_method = data[27]; + ctx->ihdr.interlace_method = data[28]; + + if(!ctx->max_width) ctx->max_width = png_u32max; + if(!ctx->max_height) ctx->max_height = png_u32max; + + ret = check_ihdr(&ctx->ihdr, ctx->max_width, ctx->max_height); + if(ret) return ret; + + ctx->file.ihdr = 1; + ctx->stored.ihdr = 1; + + ctx->channels = 1; /* grayscale or indexed color */ + + if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR) ctx->channels = 3; + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) ctx->channels = 2; + else if(ctx->ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) ctx->channels = 4; + + if(ctx->ihdr.bit_depth < 8) ctx->bytes_per_pixel = 1; + else ctx->bytes_per_pixel = ctx->channels * (ctx->ihdr.bit_depth / 8); + + ret = calculate_subimages(ctx); + if(ret) return ret; + + return 0; +} + +static void splt_undo(spng_ctx *ctx) +{ + struct spng_splt *splt = &ctx->splt_list[ctx->n_splt - 1]; + + spng__free(ctx, splt->entries); + + decrease_cache_usage(ctx, sizeof(struct spng_splt)); + decrease_cache_usage(ctx, splt->n_entries * sizeof(struct spng_splt_entry)); + + splt->entries = NULL; + + ctx->n_splt--; +} + +static void text_undo(spng_ctx *ctx) +{ + struct spng_text2 *text = &ctx->text_list[ctx->n_text - 1]; + + spng__free(ctx, text->keyword); + if(text->compression_flag) spng__free(ctx, text->text); + + decrease_cache_usage(ctx, text->cache_usage); + decrease_cache_usage(ctx, sizeof(struct spng_text2)); + + text->keyword = NULL; + text->text = NULL; + + ctx->n_text--; +} + +static int read_non_idat_chunks(spng_ctx *ctx) +{ + int ret; + struct spng_chunk chunk; + const unsigned char *data; + + ctx->discard = 0; + ctx->undo = NULL; + ctx->prev_stored = ctx->stored; + + while( !(ret = read_header(ctx))) + { + if(ctx->discard) + { + if(ctx->undo) ctx->undo(ctx); + + ctx->stored = ctx->prev_stored; + } + + ctx->discard = 0; + ctx->undo = NULL; + + ctx->prev_stored = ctx->stored; + chunk = ctx->current_chunk; + + if(!memcmp(chunk.type, type_idat, 4)) + { + if(ctx->state < SPNG_STATE_FIRST_IDAT) + { + if(ctx->ihdr.color_type == 3 && !ctx->stored.plte) return SPNG_ENOPLTE; + + ctx->first_idat = chunk; + return 0; + } + + if(ctx->prev_was_idat) + { + /* Ignore extra IDAT's */ + ret = discard_chunk_bytes(ctx, chunk.length); + if(ret) return ret; + + continue; + } + else return SPNG_ECHUNK_POS; /* IDAT chunk not at the end of the IDAT sequence */ + } + + ctx->prev_was_idat = 0; + + if(is_small_chunk(chunk.type)) + { + /* None of the known chunks can be zero length */ + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + /* The largest of these chunks is PLTE with 256 entries */ + ret = read_chunk_bytes(ctx, chunk.length > 768 ? 768 : chunk.length); + if(ret) return ret; + } + + data = ctx->data; + + if(is_critical_chunk(&chunk)) + { + if(!memcmp(chunk.type, type_plte, 4)) + { + if(ctx->file.trns || ctx->file.hist || ctx->file.bkgd) return SPNG_ECHUNK_POS; + if(chunk.length % 3 != 0) return SPNG_ECHUNK_SIZE; + + ctx->plte.n_entries = chunk.length / 3; + + if(check_plte(&ctx->plte, &ctx->ihdr)) return SPNG_ECHUNK_SIZE; /* XXX: EPLTE? */ + + size_t i; + for(i=0; i < ctx->plte.n_entries; i++) + { + ctx->plte.entries[i].red = data[i * 3]; + ctx->plte.entries[i].green = data[i * 3 + 1]; + ctx->plte.entries[i].blue = data[i * 3 + 2]; + } + + ctx->file.plte = 1; + ctx->stored.plte = 1; + } + else if(!memcmp(chunk.type, type_iend, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) + { + if(chunk.length) return SPNG_ECHUNK_SIZE; + + ret = read_and_check_crc(ctx); + if(ret == -SPNG_CRC_DISCARD) ret = 0; + + return ret; + } + else return SPNG_ECHUNK_POS; + } + else if(!memcmp(chunk.type, type_ihdr, 4)) return SPNG_ECHUNK_POS; + else return SPNG_ECHUNK_UNKNOWN_CRITICAL; + } + else if(!memcmp(chunk.type, type_chrm, 4)) /* Ancillary chunks */ + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.chrm) return SPNG_EDUP_CHRM; + + if(chunk.length != 32) return SPNG_ECHUNK_SIZE; + + ctx->chrm_int.white_point_x = read_u32(data); + ctx->chrm_int.white_point_y = read_u32(data + 4); + ctx->chrm_int.red_x = read_u32(data + 8); + ctx->chrm_int.red_y = read_u32(data + 12); + ctx->chrm_int.green_x = read_u32(data + 16); + ctx->chrm_int.green_y = read_u32(data + 20); + ctx->chrm_int.blue_x = read_u32(data + 24); + ctx->chrm_int.blue_y = read_u32(data + 28); + + if(check_chrm_int(&ctx->chrm_int)) return SPNG_ECHRM; + + ctx->file.chrm = 1; + ctx->stored.chrm = 1; + } + else if(!memcmp(chunk.type, type_gama, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.gama) return SPNG_EDUP_GAMA; + + if(chunk.length != 4) return SPNG_ECHUNK_SIZE; + + ctx->gama = read_u32(data); + + if(!ctx->gama) return SPNG_EGAMA; + if(ctx->gama > png_u32max) return SPNG_EGAMA; + + ctx->file.gama = 1; + ctx->stored.gama = 1; + } + else if(!memcmp(chunk.type, type_sbit, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.sbit) return SPNG_EDUP_SBIT; + + if(ctx->ihdr.color_type == 0) + { + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + + ctx->sbit.grayscale_bits = data[0]; + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 3) + { + if(chunk.length != 3) return SPNG_ECHUNK_SIZE; + + ctx->sbit.red_bits = data[0]; + ctx->sbit.green_bits = data[1]; + ctx->sbit.blue_bits = data[2]; + } + else if(ctx->ihdr.color_type == 4) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->sbit.grayscale_bits = data[0]; + ctx->sbit.alpha_bits = data[1]; + } + else if(ctx->ihdr.color_type == 6) + { + if(chunk.length != 4) return SPNG_ECHUNK_SIZE; + + ctx->sbit.red_bits = data[0]; + ctx->sbit.green_bits = data[1]; + ctx->sbit.blue_bits = data[2]; + ctx->sbit.alpha_bits = data[3]; + } + + if(check_sbit(&ctx->sbit, &ctx->ihdr)) return SPNG_ESBIT; + + ctx->file.sbit = 1; + ctx->stored.sbit = 1; + } + else if(!memcmp(chunk.type, type_srgb, 4)) + { + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.srgb) return SPNG_EDUP_SRGB; + + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + + ctx->srgb_rendering_intent = data[0]; + + if(ctx->srgb_rendering_intent > 3) return SPNG_ESRGB; + + ctx->file.srgb = 1; + ctx->stored.srgb = 1; + } + else if(!memcmp(chunk.type, type_bkgd, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.bkgd) return SPNG_EDUP_BKGD; + + uint16_t mask = ~0; + if(ctx->ihdr.bit_depth < 16) mask = (1 << ctx->ihdr.bit_depth) - 1; + + if(ctx->ihdr.color_type == 0 || ctx->ihdr.color_type == 4) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->bkgd.gray = read_u16(data) & mask; + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 6) + { + if(chunk.length != 6) return SPNG_ECHUNK_SIZE; + + ctx->bkgd.red = read_u16(data) & mask; + ctx->bkgd.green = read_u16(data + 2) & mask; + ctx->bkgd.blue = read_u16(data + 4) & mask; + } + else if(ctx->ihdr.color_type == 3) + { + if(chunk.length != 1) return SPNG_ECHUNK_SIZE; + if(!ctx->file.plte) return SPNG_EBKGD_NO_PLTE; + + ctx->bkgd.plte_index = data[0]; + if(ctx->bkgd.plte_index >= ctx->plte.n_entries) return SPNG_EBKGD_PLTE_IDX; + } + + ctx->file.bkgd = 1; + ctx->stored.bkgd = 1; + } + else if(!memcmp(chunk.type, type_trns, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.trns) return SPNG_EDUP_TRNS; + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + uint16_t mask = ~0; + if(ctx->ihdr.bit_depth < 16) mask = (1 << ctx->ihdr.bit_depth) - 1; + + if(ctx->ihdr.color_type == 0) + { + if(chunk.length != 2) return SPNG_ECHUNK_SIZE; + + ctx->trns.gray = read_u16(data) & mask; + } + else if(ctx->ihdr.color_type == 2) + { + if(chunk.length != 6) return SPNG_ECHUNK_SIZE; + + ctx->trns.red = read_u16(data) & mask; + ctx->trns.green = read_u16(data + 2) & mask; + ctx->trns.blue = read_u16(data + 4) & mask; + } + else if(ctx->ihdr.color_type == 3) + { + if(chunk.length > ctx->plte.n_entries) return SPNG_ECHUNK_SIZE; + if(!ctx->file.plte) return SPNG_ETRNS_NO_PLTE; + + size_t k; + for(k=0; k < chunk.length; k++) + { + ctx->trns.type3_alpha[k] = data[k]; + } + ctx->trns.n_type3_entries = chunk.length; + } + + if(ctx->ihdr.color_type == 4 || ctx->ihdr.color_type == 6) return SPNG_ETRNS_COLOR_TYPE; + + ctx->file.trns = 1; + ctx->stored.trns = 1; + } + else if(!memcmp(chunk.type, type_hist, 4)) + { + if(!ctx->file.plte) return SPNG_EHIST_NO_PLTE; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.hist) return SPNG_EDUP_HIST; + + if( (chunk.length / 2) != (ctx->plte.n_entries) ) return SPNG_ECHUNK_SIZE; + + size_t k; + for(k=0; k < (chunk.length / 2); k++) + { + ctx->hist.frequency[k] = read_u16(data + k*2); + } + + ctx->file.hist = 1; + ctx->stored.hist = 1; + } + else if(!memcmp(chunk.type, type_phys, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.phys) return SPNG_EDUP_PHYS; + + if(chunk.length != 9) return SPNG_ECHUNK_SIZE; + + ctx->phys.ppu_x = read_u32(data); + ctx->phys.ppu_y = read_u32(data + 4); + ctx->phys.unit_specifier = data[8]; + + if(check_phys(&ctx->phys)) return SPNG_EPHYS; + + ctx->file.phys = 1; + ctx->stored.phys = 1; + } + else if(!memcmp(chunk.type, type_time, 4)) + { + if(ctx->file.time) return SPNG_EDUP_TIME; + + if(chunk.length != 7) return SPNG_ECHUNK_SIZE; + + struct spng_time time; + + time.year = read_u16(data); + time.month = data[2]; + time.day = data[3]; + time.hour = data[4]; + time.minute = data[5]; + time.second = data[6]; + + if(check_time(&time)) return SPNG_ETIME; + + ctx->file.time = 1; + + if(!ctx->user.time) ctx->time = time; + + ctx->stored.time = 1; + } + else if(!memcmp(chunk.type, type_offs, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.offs) return SPNG_EDUP_OFFS; + + if(chunk.length != 9) return SPNG_ECHUNK_SIZE; + + ctx->offs.x = read_s32(data); + ctx->offs.y = read_s32(data + 4); + ctx->offs.unit_specifier = data[8]; + + if(check_offs(&ctx->offs)) return SPNG_EOFFS; + + ctx->file.offs = 1; + ctx->stored.offs = 1; + } + else /* Arbitrary-length chunk */ + { + + if(!memcmp(chunk.type, type_exif, 4)) + { + if(ctx->file.exif) return SPNG_EDUP_EXIF; + + ctx->file.exif = 1; + + if(ctx->user.exif) goto discard; + + if(increase_cache_usage(ctx, chunk.length)) return SPNG_ECHUNK_LIMITS; + + struct spng_exif exif; + + exif.length = chunk.length; + + exif.data = spng__malloc(ctx, chunk.length); + if(exif.data == NULL) return SPNG_EMEM; + + ret = read_chunk_bytes2(ctx, exif.data, chunk.length); + if(ret) + { + spng__free(ctx, exif.data); + return ret; + } + + if(check_exif(&exif)) + { + spng__free(ctx, exif.data); + return SPNG_EEXIF; + } + + ctx->exif = exif; + + ctx->stored.exif = 1; + } + else if(!memcmp(chunk.type, type_iccp, 4)) + {/* TODO: add test file with color profile */ + if(ctx->file.plte) return SPNG_ECHUNK_POS; + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->file.iccp) return SPNG_EDUP_ICCP; + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.iccp = 1; + + uint32_t peek_bytes = 81 > chunk.length ? chunk.length : 81; + + ret = read_chunk_bytes(ctx, peek_bytes); + if(ret) return ret; + + unsigned char *keyword_nul = memchr(ctx->data, '\0', peek_bytes); + if(keyword_nul == NULL) return SPNG_EICCP_NAME; + + uint32_t keyword_len = keyword_nul - ctx->data; + + if(keyword_len > 79) return SPNG_EICCP_NAME; + + memcpy(ctx->iccp.profile_name, ctx->data, keyword_len); + + if(check_png_keyword(ctx->iccp.profile_name)) return SPNG_EICCP_NAME; + + if(chunk.length < (keyword_len + 2)) return SPNG_ECHUNK_SIZE; + + if(ctx->data[keyword_len + 1] != 0) return SPNG_EICCP_COMPRESSION_METHOD; + + ret = spng__inflate_stream(ctx, &ctx->iccp.profile, &ctx->iccp.profile_len, 0, ctx->data + keyword_len + 2, peek_bytes - (keyword_len + 2)); + + if(ret) return ret; + + ctx->stored.iccp = 1; + } + else if(!memcmp(chunk.type, type_text, 4) || + !memcmp(chunk.type, type_ztxt, 4) || + !memcmp(chunk.type, type_itxt, 4)) + { + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.text = 1; + + if(ctx->user.text) goto discard; + + if(increase_cache_usage(ctx, sizeof(struct spng_text2))) return SPNG_ECHUNK_LIMITS; + + ctx->n_text++; + if(ctx->n_text < 1) return SPNG_EOVERFLOW; + if(sizeof(struct spng_text2) > SIZE_MAX / ctx->n_text) return SPNG_EOVERFLOW; + + void *buf = spng__realloc(ctx, ctx->text_list, ctx->n_text * sizeof(struct spng_text2)); + if(buf == NULL) return SPNG_EMEM; + ctx->text_list = buf; + + struct spng_text2 *text = &ctx->text_list[ctx->n_text - 1]; + memset(text, 0, sizeof(struct spng_text2)); + + ctx->undo = text_undo; + + uint32_t text_offset = 0, language_tag_offset = 0, translated_keyword_offset = 0; + uint32_t peek_bytes = 256; /* enough for 3 80-byte keywords and some text bytes */ + uint32_t keyword_len; + + if(peek_bytes > chunk.length) peek_bytes = chunk.length; + + ret = read_chunk_bytes(ctx, peek_bytes); + if(ret) return ret; + + data = ctx->data; + + const unsigned char *zlib_stream = NULL; + const unsigned char *peek_end = data + peek_bytes; + const unsigned char *keyword_nul = memchr(data, 0, chunk.length > 80 ? 80 : chunk.length); + + if(keyword_nul == NULL) return SPNG_ETEXT_KEYWORD; + + keyword_len = keyword_nul - data; + + if(!memcmp(chunk.type, type_text, 4)) + { + text->type = SPNG_TEXT; + + text->text_length = chunk.length - keyword_len - 1; + + text_offset = keyword_len; + + /* increment past nul if there is a text field */ + if(text->text_length) text_offset++; + } + else if(!memcmp(chunk.type, type_ztxt, 4)) + { + text->type = SPNG_ZTXT; + + if((peek_bytes - keyword_len) <= 2) return SPNG_EZTXT; + + if(keyword_nul[1]) return SPNG_EZTXT_COMPRESSION_METHOD; + + text->compression_flag = 1; + + text_offset = keyword_len + 2; + } + else if(!memcmp(chunk.type, type_itxt, 4)) + { + text->type = SPNG_ITXT; + + /* at least two 1-byte fields, two >=0 length strings, and one byte of (compressed) text */ + if((peek_bytes - keyword_len) < 5) return SPNG_EITXT; + + text->compression_flag = keyword_nul[1]; + + if(text->compression_flag > 1) return SPNG_EITXT_COMPRESSION_FLAG; + + if(keyword_nul[2]) return SPNG_EITXT_COMPRESSION_METHOD; + + language_tag_offset = keyword_len + 3; + + const unsigned char *term; + term = memchr(data + language_tag_offset, 0, peek_bytes - language_tag_offset); + if(term == NULL) return SPNG_EITXT_LANG_TAG; + + if((peek_end - term) < 2) return SPNG_EITXT; + + translated_keyword_offset = term - data + 1; + + const unsigned char *zlib_stream = memchr(data + translated_keyword_offset, 0, peek_bytes - translated_keyword_offset); + if(zlib_stream == NULL) return SPNG_EITXT; + if(zlib_stream == peek_end) return SPNG_EITXT; + + text_offset = zlib_stream - data + 1; + text->text_length = chunk.length - text_offset; + } + else return SPNG_EINTERNAL; + + + if(text->compression_flag) + { + /* cache usage = peek_bytes + decompressed text size + nul */ + if(increase_cache_usage(ctx, peek_bytes)) return SPNG_ECHUNK_LIMITS; + + text->keyword = spng__calloc(ctx, 1, peek_bytes); + if(text->keyword == NULL) return SPNG_EMEM; + + memcpy(text->keyword, data, peek_bytes); + + zlib_stream = ctx->data + text_offset; + + ret = spng__inflate_stream(ctx, &text->text, &text->text_length, 1, zlib_stream, peek_bytes - text_offset); + + if(ret) return ret; + + text->text[text->text_length - 1] = '\0'; + text->cache_usage = text->text_length + peek_bytes; + } + else + { + if(increase_cache_usage(ctx, chunk.length + 1)) return SPNG_ECHUNK_LIMITS; + + text->keyword = spng__malloc(ctx, chunk.length + 1); + if(text->keyword == NULL) return SPNG_EMEM; + + memcpy(text->keyword, data, peek_bytes); + + if(chunk.length > peek_bytes) + { + ret = read_chunk_bytes2(ctx, text->keyword + peek_bytes, chunk.length - peek_bytes); + if(ret) return ret; + } + + text->text = text->keyword + text_offset; + + text->text_length = chunk.length - text_offset; + + text->text[text->text_length] = '\0'; + text->cache_usage = chunk.length + 1; + } + + if(check_png_keyword(text->keyword)) return SPNG_ETEXT_KEYWORD; + + text->text_length = strlen(text->text); + + if(text->type != SPNG_ITXT) + { + language_tag_offset = keyword_len; + translated_keyword_offset = keyword_len; + + if(ctx->strict && check_png_text(text->text, text->text_length)) + { + if(text->type == SPNG_ZTXT) return SPNG_EZTXT; + else return SPNG_ETEXT; + } + } + + text->language_tag = text->keyword + language_tag_offset; + text->translated_keyword = text->keyword + translated_keyword_offset; + + ctx->stored.text = 1; + } + else if(!memcmp(chunk.type, type_splt, 4)) + { + if(ctx->state == SPNG_STATE_AFTER_IDAT) return SPNG_ECHUNK_POS; + if(ctx->user.splt) goto discard; /* XXX: could check profile names for uniqueness */ + if(!chunk.length) return SPNG_ECHUNK_SIZE; + + ctx->file.splt = 1; + + /* chunk.length + sizeof(struct spng_splt) + splt->n_entries * sizeof(struct spng_splt_entry) */ + if(increase_cache_usage(ctx, chunk.length + sizeof(struct spng_splt))) return SPNG_ECHUNK_LIMITS; + + ctx->n_splt++; + if(ctx->n_splt < 1) return SPNG_EOVERFLOW; + if(sizeof(struct spng_splt) > SIZE_MAX / ctx->n_splt) return SPNG_EOVERFLOW; + + void *buf = spng__realloc(ctx, ctx->splt_list, ctx->n_splt * sizeof(struct spng_splt)); + if(buf == NULL) return SPNG_EMEM; + ctx->splt_list = buf; + + struct spng_splt *splt = &ctx->splt_list[ctx->n_splt - 1]; + + memset(splt, 0, sizeof(struct spng_splt)); + + ctx->undo = splt_undo; + + void *t = spng__malloc(ctx, chunk.length); + if(t == NULL) return SPNG_EMEM; + + splt->entries = t; /* simplifies error handling */ + data = t; + + ret = read_chunk_bytes2(ctx, t, chunk.length); + if(ret) return ret; + + uint32_t keyword_len = chunk.length < 80 ? chunk.length : 80; + + const unsigned char *keyword_nul = memchr(data, 0, keyword_len); + if(keyword_nul == NULL) return SPNG_ESPLT_NAME; + + keyword_len = keyword_nul - data; + + memcpy(splt->name, data, keyword_len); + + if(check_png_keyword(splt->name)) return SPNG_ESPLT_NAME; + + uint32_t j; + for(j=0; j < (ctx->n_splt - 1); j++) + { + if(!strcmp(ctx->splt_list[j].name, splt->name)) return SPNG_ESPLT_DUP_NAME; + } + + if( (chunk.length - keyword_len) <= 2) return SPNG_ECHUNK_SIZE; + + splt->sample_depth = data[keyword_len + 1]; + + uint32_t entries_len = chunk.length - keyword_len - 2; + if(!entries_len) return SPNG_ECHUNK_SIZE; + + if(splt->sample_depth == 16) + { + if(entries_len % 10 != 0) return SPNG_ECHUNK_SIZE; + splt->n_entries = entries_len / 10; + } + else if(splt->sample_depth == 8) + { + if(entries_len % 6 != 0) return SPNG_ECHUNK_SIZE; + splt->n_entries = entries_len / 6; + } + else return SPNG_ESPLT_DEPTH; + + if(!splt->n_entries) return SPNG_ECHUNK_SIZE; + + size_t list_size = splt->n_entries; + + if(list_size > SIZE_MAX / sizeof(struct spng_splt_entry)) return SPNG_EOVERFLOW; + + list_size *= sizeof(struct spng_splt_entry); + + if(increase_cache_usage(ctx, list_size)) return SPNG_ECHUNK_LIMITS; + + splt->entries = spng__malloc(ctx, list_size); + if(splt->entries == NULL) + { + spng__free(ctx, t); + return SPNG_EMEM; + } + + data = (unsigned char*)t + keyword_len + 2; + + uint32_t k; + if(splt->sample_depth == 16) + { + for(k=0; k < splt->n_entries; k++) + { + splt->entries[k].red = read_u16(data + k * 10); + splt->entries[k].green = read_u16(data + k * 10 + 2); + splt->entries[k].blue = read_u16(data + k * 10 + 4); + splt->entries[k].alpha = read_u16(data + k * 10 + 6); + splt->entries[k].frequency = read_u16(data + k * 10 + 8); + } + } + else if(splt->sample_depth == 8) + { + for(k=0; k < splt->n_entries; k++) + { + splt->entries[k].red = data[k * 6]; + splt->entries[k].green = data[k * 6 + 1]; + splt->entries[k].blue = data[k * 6 + 2]; + splt->entries[k].alpha = data[k * 6 + 3]; + splt->entries[k].frequency = read_u16(data + k * 6 + 4); + } + } + + spng__free(ctx, t); + decrease_cache_usage(ctx, chunk.length); + + ctx->stored.splt = 1; + } + +discard: + ret = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(ret) return ret; + } + + } + + return ret; +} + +static int decode_err(spng_ctx *ctx, int err) +{ + ctx->state = SPNG_STATE_INVALID; + + return err; +} + +/* Read chunks before or after the IDAT chunks depending on state */ +static int read_chunks(spng_ctx *ctx, int only_ihdr) +{ + if(ctx == NULL) return SPNG_EINTERNAL; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->data == NULL) + { + if(ctx->encode_only) return 0; + else return SPNG_EINTERNAL; + } + + int ret = 0; + + if(ctx->state == SPNG_STATE_INPUT) + { + ret = read_ihdr(ctx); + + if(ret) return decode_err(ctx, ret); + + ctx->state = SPNG_STATE_IHDR; + } + + if(only_ihdr) return 0; + + if(ctx->state == SPNG_STATE_EOI) + { + ctx->state = SPNG_STATE_AFTER_IDAT; + ctx->prev_was_idat = 1; + } + + while(ctx->state < SPNG_STATE_FIRST_IDAT || ctx->state == SPNG_STATE_AFTER_IDAT) + { + ret = read_non_idat_chunks(ctx); + + if(!ret) + { + if(ctx->state < SPNG_STATE_FIRST_IDAT) ctx->state = SPNG_STATE_FIRST_IDAT; + else if(ctx->state == SPNG_STATE_AFTER_IDAT) ctx->state = SPNG_STATE_IEND; + } + else + { + switch(ret) + { + case SPNG_ECHUNK_POS: + case SPNG_ECHUNK_SIZE: /* size != expected size, SPNG_ECHUNK_STDLEN = invalid size */ + case SPNG_EDUP_PLTE: + case SPNG_EDUP_CHRM: + case SPNG_EDUP_GAMA: + case SPNG_EDUP_ICCP: + case SPNG_EDUP_SBIT: + case SPNG_EDUP_SRGB: + case SPNG_EDUP_BKGD: + case SPNG_EDUP_HIST: + case SPNG_EDUP_TRNS: + case SPNG_EDUP_PHYS: + case SPNG_EDUP_TIME: + case SPNG_EDUP_OFFS: + case SPNG_EDUP_EXIF: + case SPNG_ECHRM: + case SPNG_ETRNS_COLOR_TYPE: + case SPNG_ETRNS_NO_PLTE: + case SPNG_EGAMA: + case SPNG_EICCP_NAME: + case SPNG_EICCP_COMPRESSION_METHOD: + case SPNG_ESBIT: + case SPNG_ESRGB: + case SPNG_ETEXT: + case SPNG_ETEXT_KEYWORD: + case SPNG_EZTXT: + case SPNG_EZTXT_COMPRESSION_METHOD: + case SPNG_EITXT: + case SPNG_EITXT_COMPRESSION_FLAG: + case SPNG_EITXT_COMPRESSION_METHOD: + case SPNG_EITXT_LANG_TAG: + case SPNG_EITXT_TRANSLATED_KEY: + case SPNG_EBKGD_NO_PLTE: + case SPNG_EBKGD_PLTE_IDX: + case SPNG_EHIST_NO_PLTE: + case SPNG_EPHYS: + case SPNG_ESPLT_NAME: + case SPNG_ESPLT_DUP_NAME: + case SPNG_ESPLT_DEPTH: + case SPNG_ETIME: + case SPNG_EOFFS: + case SPNG_EEXIF: + case SPNG_EZLIB: + { + if(!ctx->strict && !is_critical_chunk(&ctx->current_chunk)) + { + ret = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(ret) return decode_err(ctx, ret); + + if(ctx->undo) ctx->undo(ctx); + + ctx->stored = ctx->prev_stored; + + ctx->discard = 0; + ctx->undo = NULL; + + continue; + } + else return decode_err(ctx, ret); + + break; + } + default: return decode_err(ctx, ret); + } + } + } + + return ret; +} + +int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len) +{ + if(ctx == NULL || out == NULL) return 1; + + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + struct decode_flags f = ctx->decode_flags; + + int ret; + int fmt = ctx->fmt; + + struct spng_row_info *ri = &ctx->row_info; + const struct spng_subimage *sub = ctx->subimage; + + const struct spng_ihdr *ihdr = &ctx->ihdr; + const uint16_t *gamma_lut = ctx->gamma_lut; + unsigned char *trns_px = ctx->trns_px; + const struct spng_sbit *sb = &ctx->decode_sb; + const struct spng_plte_entry16 *plte = ctx->decode_plte; + struct spng__iter iter = (ihdr->bit_depth < 16) ? spng__iter_init(ihdr->bit_depth, ctx->scanline) : (struct spng__iter){0}; + + const unsigned char *scanline = ctx->scanline; + + int pass = ri->pass; + uint8_t next_filter = 0; + size_t scanline_width = sub[pass].scanline_width; + uint32_t k; + uint32_t scanline_idx = ri->scanline_idx; + uint32_t width = sub[pass].width; + uint8_t r_8, g_8, b_8, a_8, gray_8; + uint16_t r_16, g_16, b_16, a_16, gray_16; + r_8=0; g_8=0; b_8=0; a_8=0; gray_8=0; + r_16=0; g_16=0; b_16=0; a_16=0; gray_16=0; + size_t pixel_size = 4; /* SPNG_FMT_RGBA8 */ + size_t pixel_offset = 0; + unsigned char *pixel; + unsigned processing_depth = ihdr->bit_depth; + + if(f.indexed) processing_depth = 8; + + if(fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(fmt == SPNG_FMT_RGB8) pixel_size = 3; + + if(len < sub[pass].out_width) return SPNG_EBUFSIZ; + + if(scanline_idx == (sub[pass].height - 1) && ri->pass == ctx->last_pass) + { + ret = read_scanline_bytes(ctx, ctx->scanline, scanline_width - 1); + } + else + { + ret = read_scanline_bytes(ctx, ctx->scanline, scanline_width); + if(ret) return decode_err(ctx, ret); + + next_filter = ctx->scanline[scanline_width - 1]; + if(next_filter > 4) ret = SPNG_EFILTER; + } + + if(ret) return decode_err(ctx, ret); + + if(!scanline_idx && ri->filter > 1) + { + /* prev_scanline is all zeros for the first scanline */ + memset(ctx->prev_scanline, 0, scanline_width); + } + + if(ihdr->bit_depth == 16 && fmt != SPNG_FMT_RAW) u16_row_to_host(ctx->scanline, scanline_width - 1); + + ret = defilter_scanline(ctx->prev_scanline, ctx->scanline, scanline_width, ctx->bytes_per_pixel, ri->filter); + if(ret) return decode_err(ctx, ret); + + ri->filter = next_filter; + + for(k=0; k < width; k++) + { + pixel = (unsigned char*)out + pixel_offset; + pixel_offset += pixel_size; + + if(f.same_layout) + { + if(f.zerocopy) break; + + memcpy(out, scanline, scanline_width - 1); + break; + } + + if(f.unpack) + { + unpack_scanline(out, scanline, width, ihdr->bit_depth, fmt); + break; + } + + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + if(ihdr->bit_depth == 16) + { + memcpy(&r_16, scanline + (k * 6), 2); + memcpy(&g_16, scanline + (k * 6) + 2, 2); + memcpy(&b_16, scanline + (k * 6) + 4, 2); + + a_16 = 65535; + } + else /* == 8 */ + { + if(fmt == SPNG_FMT_RGBA8) + { + rgb8_row_to_rgba8(scanline, out, width); + break; + } + + r_8 = scanline[k * 3]; + g_8 = scanline[k * 3 + 1]; + b_8 = scanline[k * 3 + 2]; + + a_8 = 255; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) + { + uint8_t entry = 0; + + if(ihdr->bit_depth == 8) + { + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + expand_row(out, scanline, plte, width, fmt); + break; + } + + entry = scanline[k]; + } + else /* < 8 */ + { + entry = get_sample(&iter); + } + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + pixel[0] = plte[entry].red; + pixel[1] = plte[entry].green; + pixel[2] = plte[entry].blue; + if(fmt == SPNG_FMT_RGBA8) pixel[3] = plte[entry].alpha; + + continue; + } + else /* RGBA16 */ + { + r_16 = plte[entry].red; + g_16 = plte[entry].green; + b_16 = plte[entry].blue; + a_16 = plte[entry].alpha; + + memcpy(pixel, &r_16, 2); + memcpy(pixel + 2, &g_16, 2); + memcpy(pixel + 4, &b_16, 2); + memcpy(pixel + 6, &a_16, 2); + + continue; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) + { + if(ihdr->bit_depth == 16) + { + memcpy(&r_16, scanline + (k * 8), 2); + memcpy(&g_16, scanline + (k * 8) + 2, 2); + memcpy(&b_16, scanline + (k * 8) + 4, 2); + memcpy(&a_16, scanline + (k * 8) + 6, 2); + } + else /* == 8 */ + { + r_8 = scanline[k * 4]; + g_8 = scanline[k * 4 + 1]; + b_8 = scanline[k * 4 + 2]; + a_8 = scanline[k * 4 + 3]; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) + { + if(ihdr->bit_depth == 16) + { + memcpy(&gray_16, scanline + k * 2, 2); + + if(f.apply_trns && ctx->trns.gray == gray_16) a_16 = 0; + else a_16 = 65535; + + r_16 = gray_16; + g_16 = gray_16; + b_16 = gray_16; + } + else /* <= 8 */ + { + gray_8 = get_sample(&iter); + + if(f.apply_trns && ctx->trns.gray == gray_8) a_8 = 0; + else a_8 = 255; + + r_8 = gray_8; g_8 = gray_8; b_8 = gray_8; + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) + { + if(ihdr->bit_depth == 16) + { + memcpy(&gray_16, scanline + (k * 4), 2); + memcpy(&a_16, scanline + (k * 4) + 2, 2); + + r_16 = gray_16; + g_16 = gray_16; + b_16 = gray_16; + } + else /* == 8 */ + { + gray_8 = scanline[k * 2]; + a_8 = scanline[k * 2 + 1]; + + r_8 = gray_8; + g_8 = gray_8; + b_8 = gray_8; + } + } + + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + if(ihdr->bit_depth == 16) + { + r_8 = r_16 >> 8; + g_8 = g_16 >> 8; + b_8 = b_16 >> 8; + a_8 = a_16 >> 8; + } + + pixel[0] = r_8; + pixel[1] = g_8; + pixel[2] = b_8; + + if(fmt == SPNG_FMT_RGBA8) pixel[3] = a_8; + } + else if(fmt == SPNG_FMT_RGBA16) + { + if(ihdr->bit_depth != 16) + { + r_16 = r_8; + g_16 = g_8; + b_16 = b_8; + a_16 = a_8; + } + + memcpy(pixel, &r_16, 2); + memcpy(pixel + 2, &g_16, 2); + memcpy(pixel + 4, &b_16, 2); + memcpy(pixel + 6, &a_16, 2); + } + }/* for(k=0; k < width; k++) */ + + if(f.apply_trns) trns_row(out, scanline, trns_px, ctx->bytes_per_pixel, &ctx->ihdr, width, fmt); + + if(f.do_scaling) scale_row(out, width, fmt, processing_depth, sb); + + if(f.apply_gamma) gamma_correct_row(out, width, fmt, gamma_lut); + + /* The previous scanline is always defiltered */ + void *t = ctx->prev_scanline; + ctx->prev_scanline = ctx->scanline; + ctx->scanline = t; + + if(ri->scanline_idx == (sub[pass].height - 1)) /* Last scanline */ + { + if(ri->pass == ctx->last_pass) + { + ctx->state = SPNG_STATE_EOI; + + if(ctx->cur_chunk_bytes_left) /* zlib stream ended before an IDAT chunk boundary */ + {/* Discard the rest of the chunk */ + int ret = discard_chunk_bytes(ctx, ctx->cur_chunk_bytes_left); + if(ret) return decode_err(ctx, ret); + } + + ctx->last_idat = ctx->current_chunk; + + return SPNG_EOI; + } + + ri->scanline_idx = 0; + ri->pass++; + + /* Skip empty passes */ + while( (!sub[ri->pass].width || !sub[ri->pass].height) && (ri->pass < ctx->last_pass)) ri->pass++; + } + else + { + ri->row_num++; + ri->scanline_idx++; + } + + if(f.interlaced) ri->row_num = adam7_y_start[ri->pass] + ri->scanline_idx * adam7_y_delta[ri->pass]; + + return 0; +} + +int spng_decode_row(spng_ctx *ctx, void *out, size_t len) +{ + if(ctx == NULL || out == NULL) return 1; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + if(len < ctx->out_width) return SPNG_EBUFSIZ; + + const struct spng_ihdr *ihdr = &ctx->ihdr; + int ret, pass = ctx->row_info.pass; + unsigned char *outptr = out; + + if(!ihdr->interlace_method || pass == 6) return spng_decode_scanline(ctx, out, len); + + ret = spng_decode_scanline(ctx, ctx->row, ctx->out_width); + if(ret && ret != SPNG_EOI) return ret; + + uint32_t k; + unsigned pixel_size = 4; /* RGBA8 */ + if(ctx->fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(ctx->fmt == SPNG_FMT_RGB8) pixel_size = 3; + else if(ctx->fmt == SPNG_FMT_G8) pixel_size = 1; + else if(ctx->fmt == SPNG_FMT_GA8) pixel_size = 2; + else if(ctx->fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) + { + if(ihdr->bit_depth < 8) + { + struct spng__iter iter = spng__iter_init(ihdr->bit_depth, ctx->row); + const uint8_t samples_per_byte = 8 / ihdr->bit_depth; + uint8_t sample; + + for(k=0; k < ctx->subimage[pass].width; k++) + { + sample = get_sample(&iter); + + size_t ioffset = adam7_x_start[pass] + k * adam7_x_delta[pass]; + + sample = sample << (iter.initial_shift - ioffset * ihdr->bit_depth % 8); + + ioffset /= samples_per_byte; + + outptr[ioffset] |= sample; + } + + return 0; + } + else pixel_size = ctx->bytes_per_pixel; + } + + for(k=0; k < ctx->subimage[pass].width; k++) + { + size_t ioffset = (adam7_x_start[pass] + (size_t) k * adam7_x_delta[pass]) * pixel_size; + + memcpy(outptr + ioffset, ctx->row + k * pixel_size, pixel_size); + } + + return 0; +} + +int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags) +{ + if(ctx == NULL) return 1; + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + int ret = spng_decoded_image_size(ctx, fmt, &ctx->total_out_size); + if(ret) return decode_err(ctx, ret); + + ret = read_chunks(ctx, 0); + if(ret) return ret; + + if( !(flags & SPNG_DECODE_PROGRESSIVE) ) + { + if(out == NULL) return 1; + if(len < ctx->total_out_size) return SPNG_EBUFSIZ; + } + + struct spng_ihdr *ihdr = &ctx->ihdr; + + ctx->out_width = ctx->total_out_size / ihdr->height; + + ret = spng__inflate_init(ctx); + if(ret) return decode_err(ctx, ret); + + ctx->zstream.avail_in = 0; + ctx->zstream.next_in = ctx->data; + + ctx->scanline_buf = spng__malloc(ctx, ctx->subimage[ctx->widest_pass].scanline_width); + ctx->prev_scanline_buf = spng__malloc(ctx, ctx->subimage[ctx->widest_pass].scanline_width); + ctx->scanline = ctx->scanline_buf; + ctx->prev_scanline = ctx->prev_scanline_buf; + + struct decode_flags f = {0}; + + ctx->fmt = fmt; + + if(ihdr->color_type == SPNG_COLOR_TYPE_INDEXED) f.indexed = 1; + + unsigned processing_depth = ihdr->bit_depth; + + if(f.indexed) processing_depth = 8; + + if(ihdr->interlace_method) + { + f.interlaced = 1; + ctx->row_buf = spng__malloc(ctx, ctx->out_width); + ctx->row = ctx->row_buf; + + if(ctx->row == NULL) return decode_err(ctx, SPNG_EMEM); + } + + if(ctx->scanline == NULL || ctx->prev_scanline == NULL) + { + return decode_err(ctx, SPNG_EMEM); + } + + f.do_scaling = 1; + if(f.indexed) f.do_scaling = 0; + + unsigned depth_target = 8; /* FMT_RGBA8, G8 */ + if(fmt == SPNG_FMT_RGBA16) depth_target = 16; + + if(flags & SPNG_DECODE_TRNS && ctx->stored.trns) f.apply_trns = 1; + else flags &= ~SPNG_DECODE_TRNS; + + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || + ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) flags &= ~SPNG_DECODE_TRNS; + + if(flags & SPNG_DECODE_GAMMA && ctx->stored.gama) f.apply_gamma = 1; + else flags &= ~SPNG_DECODE_GAMMA; + + if(flags & SPNG_DECODE_USE_SBIT && ctx->stored.sbit) f.use_sbit = 1; + else flags &= ~SPNG_DECODE_USE_SBIT; + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGBA16)) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + } + else if(fmt == SPNG_FMT_RGB8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR && + ihdr->bit_depth == depth_target) f.same_layout = 1; + + f.apply_trns = 0; /* not applicable */ + } + else if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) + { + f.same_layout = 1; + f.do_scaling = 0; + f.apply_gamma = 0; /* for now */ + f.apply_trns = 0; + } + else if(fmt == SPNG_FMT_G8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + if(ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth < 8) f.unpack = 1; + + f.apply_trns = 0; + } + else if(fmt == SPNG_FMT_GA8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth <= 8) f.unpack = 1; + } + else if(fmt == SPNG_FMT_GA16 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth == 16) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA && + ihdr->bit_depth == depth_target) f.same_layout = 1; + else if(ihdr->bit_depth == 16) f.unpack = 1; + } + + /*if(f.same_layout && !flags && !f.interlaced) f.zerocopy = 1;*/ + + uint16_t *gamma_lut = NULL; + + if(f.apply_gamma) + { + float file_gamma = (float)ctx->gama / 100000.0f; + float max; + + unsigned lut_entries; + + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + { + lut_entries = 256; + max = 255.0f; + + gamma_lut = ctx->gamma_lut8; + ctx->gamma_lut = ctx->gamma_lut8; + } + else /* SPNG_FMT_RGBA16 */ + { + lut_entries = 65536; + max = 65535.0f; + + ctx->gamma_lut16 = spng__malloc(ctx, lut_entries * sizeof(uint16_t)); + if(ctx->gamma_lut16 == NULL) return decode_err(ctx, SPNG_EMEM); + + gamma_lut = ctx->gamma_lut16; + ctx->gamma_lut = ctx->gamma_lut16; + } + + float screen_gamma = 2.2f; + float exponent = file_gamma * screen_gamma; + + if(FP_ZERO == fpclassify(exponent)) return decode_err(ctx, SPNG_EGAMA); + + exponent = 1.0f / exponent; + + unsigned i; + for(i=0; i < lut_entries; i++) + { + float c = pow((float)i / max, exponent) * max; + if(c > max) c = max; + + gamma_lut[i] = (uint16_t)c; + } + } + + struct spng_sbit *sb = &ctx->decode_sb; + + sb->red_bits = processing_depth; + sb->green_bits = processing_depth; + sb->blue_bits = processing_depth; + sb->alpha_bits = processing_depth; + sb->grayscale_bits = processing_depth; + + if(f.use_sbit) + { + if(ihdr->color_type == 0) + { + sb->grayscale_bits = ctx->sbit.grayscale_bits; + sb->alpha_bits = ihdr->bit_depth; + } + else if(ihdr->color_type == 2 || ihdr->color_type == 3) + { + sb->red_bits = ctx->sbit.red_bits; + sb->green_bits = ctx->sbit.green_bits; + sb->blue_bits = ctx->sbit.blue_bits; + sb->alpha_bits = ihdr->bit_depth; + } + else if(ihdr->color_type == 4) + { + sb->grayscale_bits = ctx->sbit.grayscale_bits; + sb->alpha_bits = ctx->sbit.alpha_bits; + } + else /* == 6 */ + { + sb->red_bits = ctx->sbit.red_bits; + sb->green_bits = ctx->sbit.green_bits; + sb->blue_bits = ctx->sbit.blue_bits; + sb->alpha_bits = ctx->sbit.alpha_bits; + } + } + + if(ihdr->bit_depth == 16 && fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGB8)) + {/* samples are scaled down by 8 bits in the decode loop */ + sb->red_bits -= 8; + sb->green_bits -= 8; + sb->blue_bits -= 8; + sb->alpha_bits -= 8; + sb->grayscale_bits -= 8; + + processing_depth = 8; + } + + /* Prevent infinite loops in sample_to_target() */ + if(!depth_target || depth_target > 16 || + !processing_depth || processing_depth > 16 || + !sb->grayscale_bits || sb->grayscale_bits > processing_depth || + !sb->alpha_bits || sb->alpha_bits > processing_depth || + !sb->red_bits || sb->red_bits > processing_depth || + !sb->green_bits || sb->green_bits > processing_depth || + !sb->blue_bits || sb->blue_bits > processing_depth) + { + return decode_err(ctx, SPNG_ESBIT); + } + + if(sb->red_bits == sb->green_bits && + sb->green_bits == sb->blue_bits && + sb->blue_bits == sb->alpha_bits && + sb->alpha_bits == processing_depth && + processing_depth == depth_target) f.do_scaling = 0; + + struct spng_plte_entry16 *plte = ctx->decode_plte; + + /* Pre-process palette entries */ + if(f.indexed) + { + uint32_t i; + for(i=0; i < 256; i++) + { + if(f.apply_trns && i < ctx->trns.n_type3_entries) + ctx->plte.entries[i].alpha = ctx->trns.type3_alpha[i]; + else + ctx->plte.entries[i].alpha = 255; + + plte[i].red = sample_to_target(ctx->plte.entries[i].red, 8, sb->red_bits, depth_target); + plte[i].green = sample_to_target(ctx->plte.entries[i].green, 8, sb->green_bits, depth_target); + plte[i].blue = sample_to_target(ctx->plte.entries[i].blue, 8, sb->blue_bits, depth_target); + plte[i].alpha = sample_to_target(ctx->plte.entries[i].alpha, 8, sb->alpha_bits, depth_target); + + if(f.apply_gamma) + { + plte[i].red = gamma_lut[plte[i].red]; + plte[i].green = gamma_lut[plte[i].green]; + plte[i].blue = gamma_lut[plte[i].blue]; + } + } + + f.apply_trns = 0; + f.apply_gamma = 0; + } + + unsigned char *trns_px = ctx->trns_px; + + if(f.apply_trns) + { + if(fmt & (SPNG_FMT_RGBA8 | SPNG_FMT_RGBA16)) + { + if(ihdr->color_type == SPNG_COLOR_TYPE_TRUECOLOR) + { + if(ihdr->bit_depth == 16) + { + memcpy(trns_px, &ctx->trns.red, 2); + memcpy(trns_px + 2, &ctx->trns.green, 2); + memcpy(trns_px + 4, &ctx->trns.blue, 2); + } + else + { + trns_px[0] = ctx->trns.red; + trns_px[1] = ctx->trns.green; + trns_px[2] = ctx->trns.blue; + } + } + } + else if(ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE) // fmt == SPNG_FMT_GA8 && + { + if(ihdr->bit_depth == 16) + { + memcpy(trns_px, &ctx->trns.gray, 2); + } + else + { + trns_px[0] = ctx->trns.gray; + } + } + } + + ctx->decode_flags = f; + + ctx->state = SPNG_STATE_DECODE_INIT; + + struct spng_row_info *ri = &ctx->row_info; + struct spng_subimage *sub = ctx->subimage; + + while(!sub[ri->pass].width || !sub[ri->pass].height) ri->pass++; + + if(f.interlaced) ri->row_num = adam7_y_start[ri->pass]; + + unsigned pixel_size = 4; /* SPNG_FMT_RGBA8 */ + + if(fmt == SPNG_FMT_RGBA16) pixel_size = 8; + else if(fmt == SPNG_FMT_RGB8) pixel_size = 3; + else if(fmt == SPNG_FMT_G8) pixel_size = 1; + else if(fmt == SPNG_FMT_GA8) pixel_size = 2; + + int i; + for(i=ri->pass; i <= ctx->last_pass; i++) + { + if(!sub[i].scanline_width) continue; + + if(fmt & (SPNG_FMT_PNG | SPNG_FMT_RAW)) sub[i].out_width = sub[i].scanline_width - 1; + else sub[i].out_width = (size_t)sub[i].width * pixel_size; + + if(sub[i].out_width > UINT32_MAX) return decode_err(ctx, SPNG_EOVERFLOW); + } + + /* Read the first filter byte, offsetting all reads by 1 byte. + The scanlines will be aligned with the start of the array with + the next scanline's filter byte at the end, + the last scanline will end up being 1 byte "shorter". */ + ret = read_scanline_bytes(ctx, &ri->filter, 1); + if(ret) return decode_err(ctx, ret); + + if(ri->filter > 4) return decode_err(ctx, SPNG_EFILTER); + + if(flags & SPNG_DECODE_PROGRESSIVE) + { + return 0; + } + + do + { + size_t ioffset = ri->row_num * ctx->out_width; + + ret = spng_decode_row(ctx, (unsigned char*)out + ioffset, ctx->out_width); + }while(!ret); + + if(ret != SPNG_EOI) return decode_err(ctx, ret); + + return 0; +} + +int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info) +{ + if(ctx == NULL || row_info == NULL || ctx->state < SPNG_STATE_DECODE_INIT) return 1; + + if(ctx->state >= SPNG_STATE_EOI) return SPNG_EOI; + + *row_info = ctx->row_info; + + return 0; +} + +spng_ctx *spng_ctx_new(int flags) +{ + struct spng_alloc alloc = {0}; + + alloc.malloc_fn = malloc; + alloc.realloc_fn = realloc; + alloc.calloc_fn = calloc; + alloc.free_fn = free; + + return spng_ctx_new2(&alloc, flags); +} + +spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags) +{ + if(alloc == NULL) return NULL; + if(flags != (flags & SPNG_CTX_IGNORE_ADLER32)) return NULL; + + if(alloc->malloc_fn == NULL) return NULL; + if(alloc->realloc_fn == NULL) return NULL; + if(alloc->calloc_fn == NULL) return NULL; + if(alloc->free_fn == NULL) return NULL; + + spng_ctx *ctx = alloc->calloc_fn(1, sizeof(spng_ctx)); + if(ctx == NULL) return NULL; + + ctx->alloc = *alloc; + + ctx->max_chunk_size = png_u32max; + ctx->chunk_cache_limit = SIZE_MAX; + + ctx->state = SPNG_STATE_INIT; + + ctx->crc_action_critical = SPNG_CRC_ERROR; + ctx->crc_action_ancillary = SPNG_CRC_DISCARD; + + ctx->flags = flags; + + return ctx; +} + +void spng_ctx_free(spng_ctx *ctx) +{ + if(ctx == NULL) return; + + if(ctx->streaming && ctx->stream_buf != NULL) spng__free(ctx, ctx->stream_buf); + + if(!ctx->user.exif) spng__free(ctx, ctx->exif.data); + + if(!ctx->user.iccp) spng__free(ctx, ctx->iccp.profile); + + uint32_t i; + + if(ctx->splt_list != NULL && !ctx->user.splt) + { + for(i=0; i < ctx->n_splt; i++) + { + spng__free(ctx, ctx->splt_list[i].entries); + } + spng__free(ctx, ctx->splt_list); + } + + if(ctx->text_list != NULL && !ctx->user.text) + { + for(i=0; i< ctx->n_text; i++) + { + spng__free(ctx, ctx->text_list[i].keyword); + if(ctx->text_list[i].compression_flag) spng__free(ctx, ctx->text_list[i].text); + } + spng__free(ctx, ctx->text_list); + } + + inflateEnd(&ctx->zstream); + + spng__free(ctx, ctx->gamma_lut16); + + spng__free(ctx, ctx->row_buf); + spng__free(ctx, ctx->scanline_buf); + spng__free(ctx, ctx->prev_scanline_buf); + + spng_free_fn *free_func = ctx->alloc.free_fn; + + memset(ctx, 0, sizeof(spng_ctx)); + + free_func(ctx); +} + +static int buffer_read_fn(spng_ctx *ctx, void *user, void *data, size_t n) +{ + if(n > ctx->bytes_left) return SPNG_IO_EOF; + + (void)user; + (void)data; + ctx->data = ctx->data + ctx->last_read_size; + + ctx->last_read_size = n; + ctx->bytes_left -= n; + + return 0; +} + +static int file_read_fn(spng_ctx *ctx, void *user, void *data, size_t n) +{ + FILE *file = user; + (void)ctx; + + if(fread(data, n, 1, file) != 1) + { + if(feof(file)) return SPNG_IO_EOF; + else return SPNG_IO_ERROR; + } + + return 0; +} + +int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size) +{ + if(ctx == NULL || buf == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->encode_only) return SPNG_ENCODE_ONLY; + + if(ctx->data != NULL) return SPNG_EBUF_SET; + + ctx->data = buf; + ctx->png_buf = buf; + ctx->data_size = size; + ctx->bytes_left = size; + + ctx->read_fn = buffer_read_fn; + + ctx->state = SPNG_STATE_INPUT; + + return 0; +} + +int spng_set_png_stream(spng_ctx *ctx, spng_read_fn *read_func, void *user) +{ + if(ctx == NULL || read_func == NULL) return 1; + if(!ctx->state) return SPNG_EBADSTATE; + if(ctx->encode_only) return SPNG_ENCODE_ONLY; + + if(ctx->stream_buf != NULL) return SPNG_EBUF_SET; + + ctx->stream_buf = spng__malloc(ctx, SPNG_READ_SIZE); + if(ctx->stream_buf == NULL) return SPNG_EMEM; + + ctx->data = ctx->stream_buf; + ctx->data_size = SPNG_READ_SIZE; + + ctx->read_fn = read_func; + ctx->read_user_ptr = user; + + ctx->streaming = 1; + + ctx->state = SPNG_STATE_INPUT; + + return 0; +} + +int spng_set_png_file(spng_ctx *ctx, FILE *file) +{ + if(file == NULL) return 1; + + return spng_set_png_stream(ctx, file_read_fn, file); +} + +int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height) +{ + if(ctx == NULL) return 1; + + if(width > png_u32max || height > png_u32max) return 1; + + ctx->max_width = width; + ctx->max_height = height; + + return 0; +} + +int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height) +{ + if(ctx == NULL || width == NULL || height == NULL) return 1; + + *width = ctx->max_width; + *height = ctx->max_height; + + return 0; +} + +int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_limit) +{ + if(ctx == NULL || chunk_size > png_u32max || chunk_size > cache_limit) return 1; + + ctx->max_chunk_size = chunk_size; + + ctx->chunk_cache_limit = cache_limit; + + return 0; +} + +int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_limit) +{ + if(ctx == NULL || chunk_size == NULL || cache_limit == NULL) return 1; + + *chunk_size = ctx->max_chunk_size; + + *cache_limit = ctx->chunk_cache_limit; + + return 0; +} + +int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary) +{ + if(ctx == NULL) return 1; + + if(critical > 2 || critical < 0) return 1; + if(ancillary > 2 || ancillary < 0) return 1; + + if(critical == SPNG_CRC_DISCARD) return 1; + + ctx->crc_action_critical = critical; + ctx->crc_action_ancillary = ancillary; + + return 0; +} + +int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len) +{ + if(ctx == NULL || len == NULL) return 1; + + int ret = read_chunks(ctx, 1); + if(ret) return ret; + + struct spng_ihdr *ihdr = &ctx->ihdr; + size_t res = ihdr->width; + unsigned bytes_per_pixel; + + /* Currently all enums are single-bit values */ + if(fmt & ((unsigned)fmt - 1)) return SPNG_EFMT; /* More than one bit is set */ + + if(fmt == SPNG_FMT_RGBA8) + { + bytes_per_pixel = 4; + } + else if(fmt == SPNG_FMT_RGBA16) + { + bytes_per_pixel = 8; + } + else if(fmt == SPNG_FMT_RGB8) + { + bytes_per_pixel = 3; + } + else if(fmt == SPNG_FMT_PNG || fmt == SPNG_FMT_RAW) + { + ret = calculate_scanline_width(ctx, ihdr->width, &res); + if(ret) return ret; + + res -= 1; /* exclude filter byte */ + bytes_per_pixel = 1; + } + else if(fmt == SPNG_FMT_G8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + bytes_per_pixel = 1; + } + else if(fmt == SPNG_FMT_GA8 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth <= 8) + { + bytes_per_pixel = 2; + } + else if(fmt == SPNG_FMT_GA16 && ihdr->color_type == SPNG_COLOR_TYPE_GRAYSCALE && ihdr->bit_depth == 16) + { + bytes_per_pixel = 4; + } + else return SPNG_EFMT; + + if(res > SIZE_MAX / bytes_per_pixel) return SPNG_EOVERFLOW; + res = res * bytes_per_pixel; + + if(res > SIZE_MAX / ihdr->height) return SPNG_EOVERFLOW; + res = res * ihdr->height; + + *len = res; + + return 0; +} + +int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 1); + if(ret) return ret; + if(ihdr == NULL) return 1; + + *ihdr = ctx->ihdr; + + return 0; +} + +int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte) +{ + SPNG_GET_CHUNK_BOILERPLATE(plte); + + *plte = ctx->plte; + + return 0; +} + +int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns) +{ + SPNG_GET_CHUNK_BOILERPLATE(trns); + + *trns = ctx->trns; + + return 0; +} + +int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm) +{ + SPNG_GET_CHUNK_BOILERPLATE(chrm); + + chrm->white_point_x = (double)ctx->chrm_int.white_point_x / 100000.0; + chrm->white_point_y = (double)ctx->chrm_int.white_point_y / 100000.0; + chrm->red_x = (double)ctx->chrm_int.red_x / 100000.0; + chrm->red_y = (double)ctx->chrm_int.red_y / 100000.0; + chrm->blue_y = (double)ctx->chrm_int.blue_y / 100000.0; + chrm->blue_x = (double)ctx->chrm_int.blue_x / 100000.0; + chrm->green_x = (double)ctx->chrm_int.green_x / 100000.0; + chrm->green_y = (double)ctx->chrm_int.green_y / 100000.0; + + return 0; +} + +int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm) +{ + SPNG_GET_CHUNK_BOILERPLATE(chrm); + + *chrm = ctx->chrm_int; + + return 0; +} + +int spng_get_gama(spng_ctx *ctx, double *gamma) +{ + double *gama = gamma; + SPNG_GET_CHUNK_BOILERPLATE(gama); + + *gama = (double)ctx->gama / 100000.0; + + return 0; +} + +int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp) +{ + SPNG_GET_CHUNK_BOILERPLATE(iccp); + + *iccp = ctx->iccp; + + return 0; +} + +int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit) +{ + SPNG_GET_CHUNK_BOILERPLATE(sbit); + + *sbit = ctx->sbit; + + return 0; +} + +int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent) +{ + uint8_t *srgb = rendering_intent; + SPNG_GET_CHUNK_BOILERPLATE(srgb); + + *srgb = ctx->srgb_rendering_intent; + + return 0; +} + +int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 0); + if(ret) return ret; + if(!ctx->stored.text) return SPNG_ECHUNKAVAIL; + if(n_text == NULL) return 1; + + if(text == NULL) + { + *n_text = ctx->n_text; + return 0; + } + + if(*n_text < ctx->n_text) return 1; + + uint32_t i; + for(i=0; i< ctx->n_text; i++) + { + text[i].type = ctx->text_list[i].type; + memcpy(&text[i].keyword, ctx->text_list[i].keyword, strlen(ctx->text_list[i].keyword) + 1); + text[i].compression_method = 0; + text[i].compression_flag = ctx->text_list[i].compression_flag; + text[i].language_tag = ctx->text_list[i].language_tag; + text[i].translated_keyword = ctx->text_list[i].translated_keyword; + text[i].length = ctx->text_list[i].text_length; + text[i].text = ctx->text_list[i].text; + } + + return ret; +} + +int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd) +{ + SPNG_GET_CHUNK_BOILERPLATE(bkgd); + + *bkgd = ctx->bkgd; + + return 0; +} + +int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist) +{ + SPNG_GET_CHUNK_BOILERPLATE(hist); + + *hist = ctx->hist; + + return 0; +} + +int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys) +{ + SPNG_GET_CHUNK_BOILERPLATE(phys); + + *phys = ctx->phys; + + return 0; +} + +int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt) +{ + if(ctx == NULL) return 1; + int ret = read_chunks(ctx, 0); + if(ret) return ret; + if(!ctx->stored.splt) return SPNG_ECHUNKAVAIL; + if(n_splt == NULL) return 1; + + if(splt == NULL) + { + *n_splt = ctx->n_splt; + return 0; + } + + if(*n_splt < ctx->n_splt) return 1; + + memcpy(splt, &ctx->splt_list, ctx->n_splt * sizeof(struct spng_splt)); + + return 0; +} + +int spng_get_time(spng_ctx *ctx, struct spng_time *time) +{ + SPNG_GET_CHUNK_BOILERPLATE(time); + + *time = ctx->time; + + return 0; +} + +int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs) +{ + SPNG_GET_CHUNK_BOILERPLATE(offs); + + *offs = ctx->offs; + + return 0; +} + +int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif) +{ + SPNG_GET_CHUNK_BOILERPLATE(exif); + + *exif = ctx->exif; + + return 0; +} + +int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr) +{ + SPNG_SET_CHUNK_BOILERPLATE(ihdr); + + if(ctx->stored.ihdr) return 1; + + ret = check_ihdr(ihdr, ctx->max_width, ctx->max_height); + if(ret) return ret; + + ctx->ihdr = *ihdr; + + ctx->stored.ihdr = 1; + ctx->user.ihdr = 1; + + return 0; +} + +int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte) +{ + SPNG_SET_CHUNK_BOILERPLATE(plte); + + if(!ctx->stored.ihdr) return 1; + + if(check_plte(plte, &ctx->ihdr)) return 1; + + ctx->plte = *plte; + + ctx->stored.plte = 1; + ctx->user.plte = 1; + + return 0; +} + +int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns) +{ + SPNG_SET_CHUNK_BOILERPLATE(trns); + + if(!ctx->stored.ihdr) return 1; + + uint16_t mask = ~0; + if(ctx->ihdr.bit_depth < 16) mask = (1 << ctx->ihdr.bit_depth) - 1; + + if(ctx->ihdr.color_type == 0) + { + trns->gray &= mask; + } + else if(ctx->ihdr.color_type == 2) + { + trns->red &= mask; + trns->green &= mask; + trns->blue &= mask; + } + else if(ctx->ihdr.color_type == 3) + { + if(!ctx->stored.plte) return SPNG_ETRNS_NO_PLTE; + } + else return SPNG_ETRNS_COLOR_TYPE; + + ctx->trns = *trns; + + ctx->stored.trns = 1; + ctx->user.trns = 1; + + return 0; +} + +int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm) +{ + SPNG_SET_CHUNK_BOILERPLATE(chrm); + + struct spng_chrm_int chrm_int; + + chrm_int.white_point_x = (uint32_t)(chrm->white_point_x * 100000.0); + chrm_int.white_point_y = (uint32_t)(chrm->white_point_y * 100000.0); + chrm_int.red_x = (uint32_t)(chrm->red_x * 100000.0); + chrm_int.red_y = (uint32_t)(chrm->red_y * 100000.0); + chrm_int.green_x = (uint32_t)(chrm->green_x * 100000.0); + chrm_int.green_y = (uint32_t)(chrm->green_y * 100000.0); + chrm_int.blue_x = (uint32_t)(chrm->blue_x * 100000.0); + chrm_int.blue_y = (uint32_t)(chrm->blue_y * 100000.0); + + if(check_chrm_int(&chrm_int)) return SPNG_ECHRM; + + ctx->chrm_int = chrm_int; + + ctx->stored.chrm = 1; + ctx->user.chrm = 1; + + return 0; +} + +int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int) +{ + SPNG_SET_CHUNK_BOILERPLATE(chrm_int); + + if(check_chrm_int(chrm_int)) return SPNG_ECHRM; + + ctx->chrm_int = *chrm_int; + + ctx->stored.chrm = 1; + ctx->user.chrm = 1; + + return 0; +} + +int spng_set_gama(spng_ctx *ctx, double gamma) +{ + SPNG_SET_CHUNK_BOILERPLATE(ctx); + + uint32_t gama = gamma * 100000.0; + + if(!gama) return 1; + if(gama > png_u32max) return 1; + + ctx->gama = gama; + + ctx->stored.gama = 1; + ctx->user.gama = 1; + + return 0; +} + +int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp) +{ + SPNG_SET_CHUNK_BOILERPLATE(iccp); + + if(check_png_keyword(iccp->profile_name)) return SPNG_EICCP_NAME; + if(!iccp->profile_len) return 1; + + if(ctx->iccp.profile && !ctx->user.iccp) spng__free(ctx, ctx->iccp.profile); + + ctx->iccp = *iccp; + + ctx->stored.iccp = 1; + ctx->user.iccp = 1; + + return 0; +} + +int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit) +{ + SPNG_SET_CHUNK_BOILERPLATE(sbit); + + if(check_sbit(sbit, &ctx->ihdr)) return 1; + + if(!ctx->stored.ihdr) return 1; + + ctx->sbit = *sbit; + + ctx->stored.sbit = 1; + ctx->user.sbit = 1; + + return 0; +} + +int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent) +{ + SPNG_SET_CHUNK_BOILERPLATE(ctx); + + if(rendering_intent > 3) return 1; + + ctx->srgb_rendering_intent = rendering_intent; + + ctx->stored.srgb = 1; + ctx->user.srgb = 1; + + return 0; +} + +int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text) +{ + if(!n_text) return 1; + SPNG_SET_CHUNK_BOILERPLATE(text); + + return 0; /* XXX: fix this for encode support */ +/* + uint32_t i; + for(i=0; i < n_text; i++) + { + if(check_png_keyword(text[i].keyword)) return SPNG_ETEXT_KEYWORD; + if(!text[i].length) return 1; + if(text[i].text == NULL) return 1; + + if(text[i].type == SPNG_TEXT) + { + if(check_png_text(text[i].text, text[i].length)) return 1; + } + else if(text[i].type == SPNG_ZTXT) + { + if(check_png_text(text[i].text, text[i].length)) return 1; + + if(text[i].compression_method != 0) return 1; + } + else if(text[i].type == SPNG_ITXT) + { + if(text[i].compression_flag > 1) return 1; + if(text[i].compression_method != 0) return 1; + if(text[i].language_tag == NULL) return SPNG_EITXT_LANG_TAG; + if(text[i].translated_keyword == NULL) return SPNG_EITXT_TRANSLATED_KEY; + + } + else return 1; + + } + + if(ctx->text_list != NULL && !ctx->user.text) + { + for(i=0; i < ctx->n_text; i++) + { + spng__free(ctx, ctx->text_list[i].keyword); + } + spng__free(ctx, ctx->text_list); + } + + ctx->text_list = text; + ctx->n_text = n_text; + + ctx->stored.text = 1; + ctx->user.text = 1; + + return 0;*/ +} + +int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd) +{ + SPNG_SET_CHUNK_BOILERPLATE(bkgd); + + if(!ctx->stored.ihdr) return 1; + + uint16_t mask = ~0; + + if(ctx->ihdr.bit_depth < 16) mask = (1 << ctx->ihdr.bit_depth) - 1; + + if(ctx->ihdr.color_type == 0 || ctx->ihdr.color_type == 4) + { + bkgd->gray &= mask; + } + else if(ctx->ihdr.color_type == 2 || ctx->ihdr.color_type == 6) + { + bkgd->red &= mask; + bkgd->green &= mask; + bkgd->blue &= mask; + } + else if(ctx->ihdr.color_type == 3) + { + if(!ctx->stored.bkgd) return SPNG_EBKGD_NO_PLTE; + if(bkgd->plte_index >= ctx->plte.n_entries) return SPNG_EBKGD_PLTE_IDX; + } + + ctx->bkgd = *bkgd; + + ctx->stored.bkgd = 1; + ctx->user.bkgd = 1; + + return 0; +} + +int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist) +{ + SPNG_SET_CHUNK_BOILERPLATE(hist); + + if(!ctx->stored.plte) return SPNG_EHIST_NO_PLTE; + + ctx->hist = *hist; + + ctx->stored.hist = 1; + ctx->user.hist = 1; + + return 0; +} + +int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys) +{ + SPNG_SET_CHUNK_BOILERPLATE(phys); + + if(check_phys(phys)) return SPNG_EPHYS; + + ctx->phys = *phys; + + ctx->stored.phys = 1; + ctx->user.phys = 1; + + return 0; +} + +int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt) +{ + if(!n_splt) return 1; + SPNG_SET_CHUNK_BOILERPLATE(splt); + + uint32_t i; + for(i=0; i < n_splt; i++) + { + if(check_png_keyword(splt[i].name)) return SPNG_ESPLT_NAME; + if( !(splt[i].sample_depth == 8 || splt[i].sample_depth == 16) ) return SPNG_ESPLT_DEPTH; + } + + if(ctx->stored.splt && !ctx->user.splt) + { + for(i=0; i < ctx->n_splt; i++) + { + if(ctx->splt_list[i].entries != NULL) spng__free(ctx, ctx->splt_list[i].entries); + } + spng__free(ctx, ctx->splt_list); + } + + ctx->splt_list = splt; + ctx->n_splt = n_splt; + + ctx->stored.splt = 1; + ctx->user.splt = 1; + + return 0; +} + +int spng_set_time(spng_ctx *ctx, struct spng_time *time) +{ + SPNG_SET_CHUNK_BOILERPLATE(time); + + if(check_time(time)) return SPNG_ETIME; + + ctx->time = *time; + + ctx->stored.time = 1; + ctx->user.time = 1; + + return 0; +} + +int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs) +{ + SPNG_SET_CHUNK_BOILERPLATE(offs); + + if(check_offs(offs)) return SPNG_EOFFS; + + ctx->offs = *offs; + + ctx->stored.offs = 1; + ctx->user.offs = 1; + + return 0; +} + +int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif) +{ + SPNG_SET_CHUNK_BOILERPLATE(exif); + + if(check_exif(exif)) return SPNG_EEXIF; + + if(ctx->exif.data != NULL && !ctx->user.exif) spng__free(ctx, ctx->exif.data); + + ctx->exif = *exif; + + ctx->stored.exif = 1; + ctx->user.exif = 1; + + return 0; +} + +const char *spng_strerror(int err) +{ + switch(err) + { + case SPNG_IO_EOF: return "end of stream"; + case SPNG_IO_ERROR: return "stream error"; + case SPNG_OK: return "success"; + case SPNG_EINVAL: return "invalid argument"; + case SPNG_EMEM: return "out of memory"; + case SPNG_EOVERFLOW: return "arithmetic overflow"; + case SPNG_ESIGNATURE: return "invalid signature"; + case SPNG_EWIDTH: return "invalid image width"; + case SPNG_EHEIGHT: return "invalid image height"; + case SPNG_EUSER_WIDTH: return "image width exceeds user limit"; + case SPNG_EUSER_HEIGHT: return "image height exceeds user limit"; + case SPNG_EBIT_DEPTH: return "invalid bit depth"; + case SPNG_ECOLOR_TYPE: return "invalid color type"; + case SPNG_ECOMPRESSION_METHOD: return "invalid compression method"; + case SPNG_EFILTER_METHOD: return "invalid filter method"; + case SPNG_EINTERLACE_METHOD: return "invalid interlace method"; + case SPNG_EIHDR_SIZE: return "invalid IHDR chunk size"; + case SPNG_ENOIHDR: return "missing IHDR chunk"; + case SPNG_ECHUNK_POS: return "invalid chunk position"; + case SPNG_ECHUNK_SIZE: return "invalid chunk length"; + case SPNG_ECHUNK_CRC: return "invalid chunk checksum"; + case SPNG_ECHUNK_TYPE: return "invalid chunk type"; + case SPNG_ECHUNK_UNKNOWN_CRITICAL: return "unknown critical chunk"; + case SPNG_EDUP_PLTE: return "duplicate PLTE chunk"; + case SPNG_EDUP_CHRM: return "duplicate cHRM chunk"; + case SPNG_EDUP_GAMA: return "duplicate gAMA chunk"; + case SPNG_EDUP_ICCP: return "duplicate iCCP chunk"; + case SPNG_EDUP_SBIT: return "duplicate sBIT chunk"; + case SPNG_EDUP_SRGB: return "duplicate sRGB chunk"; + case SPNG_EDUP_BKGD: return "duplicate bKGD chunk"; + case SPNG_EDUP_HIST: return "duplicate hIST chunk"; + case SPNG_EDUP_TRNS: return "duplicate tRNS chunk"; + case SPNG_EDUP_PHYS: return "duplicate pHYs chunk"; + case SPNG_EDUP_TIME: return "duplicate tIME chunk"; + case SPNG_EDUP_OFFS: return "duplicate oFFs chunk"; + case SPNG_EDUP_EXIF: return "duplicate eXIf chunk"; + case SPNG_ECHRM: return "invalid cHRM chunk"; + case SPNG_EPLTE_IDX: return "invalid palette (PLTE) index"; + case SPNG_ETRNS_COLOR_TYPE: return "tRNS chunk with incompatible color type"; + case SPNG_ETRNS_NO_PLTE: return "missing palette (PLTE) for tRNS chunk"; + case SPNG_EGAMA: return "invalid gAMA chunk"; + case SPNG_EICCP_NAME: return "invalid iCCP profile name"; + case SPNG_EICCP_COMPRESSION_METHOD: return "invalid iCCP compression method"; + case SPNG_ESBIT: return "invalid sBIT chunk"; + case SPNG_ESRGB: return "invalid sRGB chunk"; + case SPNG_ETEXT: return "invalid tEXt chunk"; + case SPNG_ETEXT_KEYWORD: return "invalid tEXt keyword"; + case SPNG_EZTXT: return "invalid zTXt chunk"; + case SPNG_EZTXT_COMPRESSION_METHOD: return "invalid zTXt compression method"; + case SPNG_EITXT: return "invalid iTXt chunk"; + case SPNG_EITXT_COMPRESSION_FLAG: return "invalid iTXt compression flag"; + case SPNG_EITXT_COMPRESSION_METHOD: return "invalid iTXt compression method"; + case SPNG_EITXT_LANG_TAG: return "invalid iTXt language tag"; + case SPNG_EITXT_TRANSLATED_KEY: return "invalid iTXt translated key"; + case SPNG_EBKGD_NO_PLTE: return "missing palette for bKGD chunk"; + case SPNG_EBKGD_PLTE_IDX: return "invalid palette index for bKGD chunk"; + case SPNG_EHIST_NO_PLTE: return "missing palette for hIST chunk"; + case SPNG_EPHYS: return "invalid pHYs chunk"; + case SPNG_ESPLT_NAME: return "invalid suggested palette name"; + case SPNG_ESPLT_DUP_NAME: return "duplicate suggested palette (sPLT) name"; + case SPNG_ESPLT_DEPTH: return "invalid suggested palette (sPLT) sample depth"; + case SPNG_ETIME: return "invalid tIME chunk"; + case SPNG_EOFFS: return "invalid oFFs chunk"; + case SPNG_EEXIF: return "invalid eXIf chunk"; + case SPNG_EIDAT_TOO_SHORT: return "IDAT stream too short"; + case SPNG_EIDAT_STREAM: return "IDAT stream error"; + case SPNG_EZLIB: return "zlib error"; + case SPNG_EFILTER: return "invalid scanline filter"; + case SPNG_EBUFSIZ: return "output buffer too small"; + case SPNG_EIO: return "i/o error"; + case SPNG_EOF: return "end of file"; + case SPNG_EBUF_SET: return "buffer already set"; + case SPNG_EBADSTATE: return "non-recoverable state"; + case SPNG_EFMT: return "invalid format"; + case SPNG_EFLAGS: return "invalid flags"; + case SPNG_ECHUNKAVAIL: return "chunk not available"; + case SPNG_ENCODE_ONLY: return "encode only context"; + case SPNG_EOI: return "reached end-of-image state"; + case SPNG_ENOPLTE: return "missing PLTE for indexed image"; + case SPNG_ECHUNK_LIMITS: return "reached chunk/cache limits"; + case SPNG_EZLIB_INIT: return "zlib init error"; + case SPNG_ECHUNK_STDLEN: return "chunk exceeds maximum standard length"; + case SPNG_EINTERNAL: return "internal error"; + default: return "unknown error"; + } +} + +const char *spng_version_string(void) +{ + return SPNG_VERSION_STRING; +} + +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +/* The following SIMD optimizations are derived from libpng source code. */ + +/* +* PNG Reference Library License version 2 +* +* Copyright (c) 1995-2019 The PNG Reference Library Authors. +* Copyright (c) 2018-2019 Cosmin Truta. +* Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. +* Copyright (c) 1996-1997 Andreas Dilger. +* Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. +* +* The software is supplied "as is", without warranty of any kind, +* express or implied, including, without limitation, the warranties +* of merchantability, fitness for a particular purpose, title, and +* non-infringement. In no event shall the Copyright owners, or +* anyone distributing the software, be liable for any damages or +* other liability, whether in contract, tort or otherwise, arising +* from, out of, or in connection with the software, or the use or +* other dealings in the software, even if advised of the possibility +* of such damage. +* +* Permission is hereby granted to use, copy, modify, and distribute +* this software, or portions hereof, for any purpose, without fee, +* subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you +* must not claim that you wrote the original software. If you +* use this software in a product, an acknowledgment in the product +* documentation would be appreciated, but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must +* not be misrepresented as being the original software. +* +* 3. This Copyright notice may not be removed or altered from any +* source or altered source distribution. +*/ + +#if defined(SPNG_X86) + +#ifndef SPNG_SSE + #define SPNG_SSE 1 +#endif + +#if defined(__GNUC__) && !defined(__clang__) + #if SPNG_SSE == 3 + #pragma GCC target("ssse3") + #elif SPNG_SSE == 4 + #pragma GCC target("sse4.1") + #else + #pragma GCC target("sse2") + #endif +#endif + +/* SSE2 optimised filter functions + * Derived from filter_neon_intrinsics.c + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2016-2017 Glenn Randers-Pehrson + * Written by Mike Klein and Matt Sarett + * Derived from arm/filter_neon_intrinsics.c + * + * This code is derived from libpng source code. + * For conditions of distribution and use, see the disclaimer + * and license above. + */ + +#include +#include +#include + +/* Functions in this file look at most 3 pixels (a,b,c) to predict the 4th (d). + * They're positioned like this: + * prev: c b + * row: a d + * The Sub filter predicts d=a, Avg d=(a+b)/2, and Paeth predicts d to be + * whichever of a, b, or c is closest to p=a+b-c. + */ + +static __m128i load4(const void* p) +{ + int tmp; + memcpy(&tmp, p, sizeof(tmp)); + return _mm_cvtsi32_si128(tmp); +} + +static void store4(void* p, __m128i v) +{ + int tmp = _mm_cvtsi128_si32(v); + memcpy(p, &tmp, sizeof(int)); +} + +static __m128i load3(const void* p) +{ + uint32_t tmp = 0; + memcpy(&tmp, p, 3); + return _mm_cvtsi32_si128(tmp); +} + +static void store3(void* p, __m128i v) +{ + int tmp = _mm_cvtsi128_si32(v); + memcpy(p, &tmp, 3); +} + +static void defilter_sub3(size_t rowbytes, unsigned char *row) +{ + /* The Sub filter predicts each pixel as the previous pixel, a. + * There is no pixel to the left of the first pixel. It's encoded directly. + * That works with our main loop if we just say that left pixel was zero. + */ + size_t rb = rowbytes; + + __m128i a, d = _mm_setzero_si128(); + + while (rb >= 4) { + a = d; d = load4(row); + d = _mm_add_epi8(d, a); + store3(row, d); + + row += 3; + rb -= 3; + } + if (rb > 0) { + a = d; d = load3(row); + d = _mm_add_epi8(d, a); + store3(row, d); + } +} + +static void defilter_sub4(size_t rowbytes, unsigned char *row) +{ + /* The Sub filter predicts each pixel as the previous pixel, a. + * There is no pixel to the left of the first pixel. It's encoded directly. + * That works with our main loop if we just say that left pixel was zero. + */ + size_t rb = rowbytes+4; + + __m128i a, d = _mm_setzero_si128(); + + while (rb > 4) { + a = d; d = load4(row); + d = _mm_add_epi8(d, a); + store4(row, d); + + row += 4; + rb -= 4; + } +} + +static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* The Avg filter predicts each pixel as the (truncated) average of a and b. + * There's no pixel to the left of the first pixel. Luckily, it's + * predicted to be half of the pixel above it. So again, this works + * perfectly with our loop if we make sure a starts at zero. + */ + + size_t rb = rowbytes; + + const __m128i zero = _mm_setzero_si128(); + + __m128i b; + __m128i a, d = zero; + + while (rb >= 4) + { + __m128i avg; + b = load4(prev); + a = d; d = load4(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a,b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + d = _mm_add_epi8(d, avg); + store3(row, d); + + prev += 3; + row += 3; + rb -= 3; + } + + if (rb > 0) + { + __m128i avg; + b = load3(prev); + a = d; d = load3(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a, b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + + d = _mm_add_epi8(d, avg); + store3(row, d); + } +} + +static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* The Avg filter predicts each pixel as the (truncated) average of a and b. + * There's no pixel to the left of the first pixel. Luckily, it's + * predicted to be half of the pixel above it. So again, this works + * perfectly with our loop if we make sure a starts at zero. + */ + size_t rb = rowbytes+4; + + const __m128i zero = _mm_setzero_si128(); + __m128i b; + __m128i a, d = zero; + + while (rb > 4) + { + __m128i avg; + b = load4(prev); + a = d; d = load4(row ); + + /* PNG requires a truncating average, so we can't just use _mm_avg_epu8 */ + avg = _mm_avg_epu8(a,b); + /* ...but we can fix it up by subtracting off 1 if it rounded up. */ + avg = _mm_sub_epi8(avg, _mm_and_si128(_mm_xor_si128(a, b), + _mm_set1_epi8(1))); + + d = _mm_add_epi8(d, avg); + store4(row, d); + + prev += 4; + row += 4; + rb -= 4; + } +} + +/* Returns |x| for 16-bit lanes. */ +#if (SPNG_SSE >= 3) && !defined(_MSC_VER) +__attribute__((target("ssse3"))) +#endif +static __m128i abs_i16(__m128i x) +{ +#if SPNG_SSE >= 3 + return _mm_abs_epi16(x); +#else + /* Read this all as, return x<0 ? -x : x. + * To negate two's complement, you flip all the bits then add 1. + */ + __m128i is_negative = _mm_cmplt_epi16(x, _mm_setzero_si128()); + + /* Flip negative lanes. */ + x = _mm_xor_si128(x, is_negative); + + /* +1 to negative lanes, else +0. */ + x = _mm_sub_epi16(x, is_negative); + return x; +#endif +} + +/* Bytewise c ? t : e. */ +static __m128i if_then_else(__m128i c, __m128i t, __m128i e) +{ +#if SPNG_SSE >= 4 + return _mm_blendv_epi8(e, t, c); +#else + return _mm_or_si128(_mm_and_si128(c, t), _mm_andnot_si128(c, e)); +#endif +} + +static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* Paeth tries to predict pixel d using the pixel to the left of it, a, + * and two pixels from the previous row, b and c: + * prev: c b + * row: a d + * The Paeth function predicts d to be whichever of a, b, or c is nearest to + * p=a+b-c. + * + * The first pixel has no left context, and so uses an Up filter, p = b. + * This works naturally with our main loop's p = a+b-c if we force a and c + * to zero. + * Here we zero b and d, which become c and a respectively at the start of + * the loop. + */ + size_t rb = rowbytes; + const __m128i zero = _mm_setzero_si128(); + __m128i c, b = zero, + a, d = zero; + + while (rb >= 4) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + __m128i pa,pb,pc,smallest,nearest; + c = b; b = _mm_unpacklo_epi8(load4(prev), zero); + a = d; d = _mm_unpacklo_epi8(load4(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store3(row, _mm_packus_epi16(d, d)); + + prev += 3; + row += 3; + rb -= 3; + } + + if (rb > 0) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + __m128i pa,pb,pc,smallest,nearest; + c = b; b = _mm_unpacklo_epi8(load3(prev), zero); + a = d; d = _mm_unpacklo_epi8(load3(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store3(row, _mm_packus_epi16(d, d)); + } +} + +static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev) +{ + /* Paeth tries to predict pixel d using the pixel to the left of it, a, + * and two pixels from the previous row, b and c: + * prev: c b + * row: a d + * The Paeth function predicts d to be whichever of a, b, or c is nearest to + * p=a+b-c. + * + * The first pixel has no left context, and so uses an Up filter, p = b. + * This works naturally with our main loop's p = a+b-c if we force a and c + * to zero. + * Here we zero b and d, which become c and a respectively at the start of + * the loop. + */ + size_t rb = rowbytes+4; + + const __m128i zero = _mm_setzero_si128(); + __m128i pa,pb,pc,smallest,nearest; + __m128i c, b = zero, + a, d = zero; + + while (rb > 4) + { + /* It's easiest to do this math (particularly, deal with pc) with 16-bit + * intermediates. + */ + c = b; b = _mm_unpacklo_epi8(load4(prev), zero); + a = d; d = _mm_unpacklo_epi8(load4(row ), zero); + + /* (p-a) == (a+b-c - a) == (b-c) */ + pa = _mm_sub_epi16(b, c); + + /* (p-b) == (a+b-c - b) == (a-c) */ + pb = _mm_sub_epi16(a, c); + + /* (p-c) == (a+b-c - c) == (a+b-c-c) == (b-c)+(a-c) */ + pc = _mm_add_epi16(pa, pb); + + pa = abs_i16(pa); /* |p-a| */ + pb = abs_i16(pb); /* |p-b| */ + pc = abs_i16(pc); /* |p-c| */ + + smallest = _mm_min_epi16(pc, _mm_min_epi16(pa, pb)); + + /* Paeth breaks ties favoring a over b over c. */ + nearest = if_then_else(_mm_cmpeq_epi16(smallest, pa), a, + if_then_else(_mm_cmpeq_epi16(smallest, pb), b, c)); + + /* Note `_epi8`: we need addition to wrap modulo 255. */ + d = _mm_add_epi8(d, nearest); + store4(row, _mm_packus_epi16(d, d)); + + prev += 4; + row += 4; + rb -= 4; + } +} + +#endif /* SPNG_X86 */ + + +#if defined(SPNG_ARM) + +/* NEON optimised filter functions + * Derived from filter_neon_intrinsics.c + * + * Copyright (c) 2018 Cosmin Truta + * Copyright (c) 2014,2016 Glenn Randers-Pehrson + * Written by James Yu , October 2013. + * Based on filter_neon.S, written by Mans Rullgard, 2011. + * + * This code is derived from libpng source code. + * For conditions of distribution and use, see the disclaimer + * and license in this file. + */ + +#define png_aligncast(type, value) ((void*)(value)) +#define png_aligncastconst(type, value) ((const void*)(value)) + +/* libpng row pointers are not necessarily aligned to any particular boundary, + * however this code will only work with appropriate alignment. mips/mips_init.c + * checks for this (and will not compile unless it is done). This code uses + * variants of png_aligncast to avoid compiler warnings. + */ +#define png_ptr(type,pointer) png_aligncast(type *,pointer) +#define png_ptrc(type,pointer) png_aligncastconst(const type *,pointer) + +/* The following relies on a variable 'temp_pointer' being declared with type + * 'type'. This is written this way just to hide the GCC strict aliasing + * warning; note that the code is safe because there never is an alias between + * the input and output pointers. + */ +#define png_ldr(type,pointer)\ + (temp_pointer = png_ptr(type,pointer), *temp_pointer) + + +#if defined(_MSC_VER) && !defined(__clang__) && defined(_M_ARM64) + #include +#else + #include +#endif + +static void defilter_sub3(size_t rowbytes, unsigned char *row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp = vld1q_u8(rp); + uint8x8x2_t *vrpt = png_ptr(uint8x8x2_t, &vtmp); + uint8x8x2_t vrp = *vrpt; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop;) + { + uint8x8_t vtmp1, vtmp2; + uint32x2_t *temp_pointer; + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vdest.val[0] = vadd_u8(vdest.val[3], vrp.val[0]); + vtmp2 = vext_u8(vrp.val[0], vrp.val[1], 6); + vdest.val[1] = vadd_u8(vdest.val[0], vtmp1); + + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + vdest.val[2] = vadd_u8(vdest.val[1], vtmp2); + vdest.val[3] = vadd_u8(vdest.val[2], vtmp1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t, &vtmp); + vrp = *vrpt; + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_sub4(size_t rowbytes, unsigned char *row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16) + { + uint32x2x4_t vtmp = vld4_u32(png_ptr(uint32_t,rp)); + uint8x8x4_t *vrpt = png_ptr(uint8x8x4_t,&vtmp); + uint8x8x4_t vrp = *vrpt; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vdest.val[0] = vadd_u8(vdest.val[3], vrp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[0], vrp.val[1]); + vdest.val[2] = vadd_u8(vdest.val[1], vrp.val[2]); + vdest.val[3] = vadd_u8(vdest.val[2], vrp.val[3]); + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +static void defilter_avg3(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + const unsigned char *pp = prev_row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp; + uint8x8x2_t *vrpt; + uint8x8x2_t vrp; + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + vtmp = vld1q_u8(rp); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + for (; rp < rp_stop; pp += 12) + { + uint8x8_t vtmp1, vtmp2, vtmp3; + + uint8x8x2_t *vppt; + uint8x8x2_t vpp; + + uint32x2_t *temp_pointer; + + vtmp = vld1q_u8(pp); + vppt = png_ptr(uint8x8x2_t,&vtmp); + vpp = *vppt; + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vdest.val[0] = vhadd_u8(vdest.val[3], vpp.val[0]); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 3); + vtmp3 = vext_u8(vrp.val[0], vrp.val[1], 6); + vdest.val[1] = vhadd_u8(vdest.val[0], vtmp2); + vdest.val[1] = vadd_u8(vdest.val[1], vtmp1); + + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 6); + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + vdest.val[2] = vhadd_u8(vdest.val[1], vtmp2); + vdest.val[2] = vadd_u8(vdest.val[2], vtmp3); + + vtmp2 = vext_u8(vpp.val[1], vpp.val[1], 1); + + vdest.val[3] = vhadd_u8(vdest.val[2], vtmp2); + vdest.val[3] = vadd_u8(vdest.val[3], vtmp1); + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_avg4(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + const unsigned char *pp = prev_row; + + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16, pp += 16) + { + uint32x2x4_t vtmp; + uint8x8x4_t *vrpt, *vppt; + uint8x8x4_t vrp, vpp; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vtmp = vld4_u32(png_ptr(uint32_t,rp)); + vrpt = png_ptr(uint8x8x4_t,&vtmp); + vrp = *vrpt; + vtmp = vld4_u32(png_ptrc(uint32_t,pp)); + vppt = png_ptr(uint8x8x4_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = vhadd_u8(vdest.val[3], vpp.val[0]); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + vdest.val[1] = vhadd_u8(vdest.val[0], vpp.val[1]); + vdest.val[1] = vadd_u8(vdest.val[1], vrp.val[1]); + vdest.val[2] = vhadd_u8(vdest.val[1], vpp.val[2]); + vdest.val[2] = vadd_u8(vdest.val[2], vrp.val[2]); + vdest.val[3] = vhadd_u8(vdest.val[2], vpp.val[3]); + vdest.val[3] = vadd_u8(vdest.val[3], vrp.val[3]); + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +static uint8x8_t paeth_arm(uint8x8_t a, uint8x8_t b, uint8x8_t c) +{ + uint8x8_t d, e; + uint16x8_t p1, pa, pb, pc; + + p1 = vaddl_u8(a, b); /* a + b */ + pc = vaddl_u8(c, c); /* c * 2 */ + pa = vabdl_u8(b, c); /* pa */ + pb = vabdl_u8(a, c); /* pb */ + pc = vabdq_u16(p1, pc); /* pc */ + + p1 = vcleq_u16(pa, pb); /* pa <= pb */ + pa = vcleq_u16(pa, pc); /* pa <= pc */ + pb = vcleq_u16(pb, pc); /* pb <= pc */ + + p1 = vandq_u16(p1, pa); /* pa <= pb && pa <= pc */ + + d = vmovn_u16(pb); + e = vmovn_u16(p1); + + d = vbsl_u8(d, b, c); + e = vbsl_u8(e, a, d); + + return e; +} + +static void defilter_paeth3(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + const unsigned char *pp = prev_row; + unsigned char *rp_stop = row + rowbytes; + + uint8x16_t vtmp; + uint8x8x2_t *vrpt; + uint8x8x2_t vrp; + uint8x8_t vlast = vdup_n_u8(0); + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + vtmp = vld1q_u8(rp); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + for (; rp < rp_stop; pp += 12) + { + uint8x8x2_t *vppt; + uint8x8x2_t vpp; + uint8x8_t vtmp1, vtmp2, vtmp3; + uint32x2_t *temp_pointer; + + vtmp = vld1q_u8(pp); + vppt = png_ptr(uint8x8x2_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = paeth_arm(vdest.val[3], vpp.val[0], vlast); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 3); + vtmp2 = vext_u8(vpp.val[0], vpp.val[1], 3); + vdest.val[1] = paeth_arm(vdest.val[0], vtmp2, vpp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[1], vtmp1); + + vtmp1 = vext_u8(vrp.val[0], vrp.val[1], 6); + vtmp3 = vext_u8(vpp.val[0], vpp.val[1], 6); + vdest.val[2] = paeth_arm(vdest.val[1], vtmp3, vtmp2); + vdest.val[2] = vadd_u8(vdest.val[2], vtmp1); + + vtmp1 = vext_u8(vrp.val[1], vrp.val[1], 1); + vtmp2 = vext_u8(vpp.val[1], vpp.val[1], 1); + + vtmp = vld1q_u8(rp + 12); + vrpt = png_ptr(uint8x8x2_t,&vtmp); + vrp = *vrpt; + + vdest.val[3] = paeth_arm(vdest.val[2], vtmp2, vtmp3); + vdest.val[3] = vadd_u8(vdest.val[3], vtmp1); + + vlast = vtmp2; + + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[0]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[1]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[2]), 0); + rp += 3; + vst1_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2_t,&vdest.val[3]), 0); + rp += 3; + } +} + +static void defilter_paeth4(size_t rowbytes, unsigned char *row, const unsigned char *prev_row) +{ + unsigned char *rp = row; + unsigned char *rp_stop = row + rowbytes; + const unsigned char *pp = prev_row; + + uint8x8_t vlast = vdup_n_u8(0); + uint8x8x4_t vdest; + vdest.val[3] = vdup_n_u8(0); + + for (; rp < rp_stop; rp += 16, pp += 16) + { + uint32x2x4_t vtmp; + uint8x8x4_t *vrpt, *vppt; + uint8x8x4_t vrp, vpp; + uint32x2x4_t *temp_pointer; + uint32x2x4_t vdest_val; + + vtmp = vld4_u32(png_ptr(uint32_t,rp)); + vrpt = png_ptr(uint8x8x4_t,&vtmp); + vrp = *vrpt; + vtmp = vld4_u32(png_ptrc(uint32_t,pp)); + vppt = png_ptr(uint8x8x4_t,&vtmp); + vpp = *vppt; + + vdest.val[0] = paeth_arm(vdest.val[3], vpp.val[0], vlast); + vdest.val[0] = vadd_u8(vdest.val[0], vrp.val[0]); + vdest.val[1] = paeth_arm(vdest.val[0], vpp.val[1], vpp.val[0]); + vdest.val[1] = vadd_u8(vdest.val[1], vrp.val[1]); + vdest.val[2] = paeth_arm(vdest.val[1], vpp.val[2], vpp.val[1]); + vdest.val[2] = vadd_u8(vdest.val[2], vrp.val[2]); + vdest.val[3] = paeth_arm(vdest.val[2], vpp.val[3], vpp.val[2]); + vdest.val[3] = vadd_u8(vdest.val[3], vrp.val[3]); + + vlast = vpp.val[3]; + + vdest_val = png_ldr(uint32x2x4_t, &vdest); + vst4_lane_u32(png_ptr(uint32_t,rp), vdest_val, 0); + } +} + +#endif /* SPNG_ARM */ diff --git a/Utilities/spng.h b/Utilities/spng.h new file mode 100644 index 00000000..6fd99731 --- /dev/null +++ b/Utilities/spng.h @@ -0,0 +1,618 @@ +/* SPDX-License-Identifier: (BSD-2-Clause AND libpng-2.0) */ +#define SPNG_STATIC +#define SPNG_USE_MINIZ + +#ifndef SPNG_H +#define SPNG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(SPNG_STATIC) + #if defined(SPNG__BUILD) + #define SPNG_API __declspec(dllexport) + #else + #define SPNG_API __declspec(dllimport) + #endif +#else + #define SPNG_API +#endif + +#include +#include +#include +#include "miniz.h" + +#define SPNG_VERSION_MAJOR 0 +#define SPNG_VERSION_MINOR 6 +#define SPNG_VERSION_PATCH 3 + +enum spng_errno +{ + SPNG_IO_ERROR = -2, + SPNG_IO_EOF = -1, + SPNG_OK = 0, + SPNG_EINVAL, + SPNG_EMEM, + SPNG_EOVERFLOW, + SPNG_ESIGNATURE, + SPNG_EWIDTH, + SPNG_EHEIGHT, + SPNG_EUSER_WIDTH, + SPNG_EUSER_HEIGHT, + SPNG_EBIT_DEPTH, + SPNG_ECOLOR_TYPE, + SPNG_ECOMPRESSION_METHOD, + SPNG_EFILTER_METHOD, + SPNG_EINTERLACE_METHOD, + SPNG_EIHDR_SIZE, + SPNG_ENOIHDR, + SPNG_ECHUNK_POS, + SPNG_ECHUNK_SIZE, + SPNG_ECHUNK_CRC, + SPNG_ECHUNK_TYPE, + SPNG_ECHUNK_UNKNOWN_CRITICAL, + SPNG_EDUP_PLTE, + SPNG_EDUP_CHRM, + SPNG_EDUP_GAMA, + SPNG_EDUP_ICCP, + SPNG_EDUP_SBIT, + SPNG_EDUP_SRGB, + SPNG_EDUP_BKGD, + SPNG_EDUP_HIST, + SPNG_EDUP_TRNS, + SPNG_EDUP_PHYS, + SPNG_EDUP_TIME, + SPNG_EDUP_OFFS, + SPNG_EDUP_EXIF, + SPNG_ECHRM, + SPNG_EPLTE_IDX, + SPNG_ETRNS_COLOR_TYPE, + SPNG_ETRNS_NO_PLTE, + SPNG_EGAMA, + SPNG_EICCP_NAME, + SPNG_EICCP_COMPRESSION_METHOD, + SPNG_ESBIT, + SPNG_ESRGB, + SPNG_ETEXT, + SPNG_ETEXT_KEYWORD, + SPNG_EZTXT, + SPNG_EZTXT_COMPRESSION_METHOD, + SPNG_EITXT, + SPNG_EITXT_COMPRESSION_FLAG, + SPNG_EITXT_COMPRESSION_METHOD, + SPNG_EITXT_LANG_TAG, + SPNG_EITXT_TRANSLATED_KEY, + SPNG_EBKGD_NO_PLTE, + SPNG_EBKGD_PLTE_IDX, + SPNG_EHIST_NO_PLTE, + SPNG_EPHYS, + SPNG_ESPLT_NAME, + SPNG_ESPLT_DUP_NAME, + SPNG_ESPLT_DEPTH, + SPNG_ETIME, + SPNG_EOFFS, + SPNG_EEXIF, + SPNG_EIDAT_TOO_SHORT, + SPNG_EIDAT_STREAM, + SPNG_EZLIB, + SPNG_EFILTER, + SPNG_EBUFSIZ, + SPNG_EIO, + SPNG_EOF, + SPNG_EBUF_SET, + SPNG_EBADSTATE, + SPNG_EFMT, + SPNG_EFLAGS, + SPNG_ECHUNKAVAIL, + SPNG_ENCODE_ONLY, + SPNG_EOI, + SPNG_ENOPLTE, + SPNG_ECHUNK_LIMITS, + SPNG_EZLIB_INIT, + SPNG_ECHUNK_STDLEN, + SPNG_EINTERNAL, +}; + +enum spng_text_type +{ + SPNG_TEXT = 1, + SPNG_ZTXT = 2, + SPNG_ITXT = 3 +}; + +enum spng_color_type +{ + SPNG_COLOR_TYPE_GRAYSCALE = 0, + SPNG_COLOR_TYPE_TRUECOLOR = 2, + SPNG_COLOR_TYPE_INDEXED = 3, + SPNG_COLOR_TYPE_GRAYSCALE_ALPHA = 4, + SPNG_COLOR_TYPE_TRUECOLOR_ALPHA = 6 +}; + +enum spng_filter +{ + SPNG_FILTER_NONE = 0, + SPNG_FILTER_SUB = 1, + SPNG_FILTER_UP = 2, + SPNG_FILTER_AVERAGE = 3, + SPNG_FILTER_PAETH = 4 +}; + +enum spng_interlace_method +{ + SPNG_INTERLACE_NONE = 0, + SPNG_INTERLACE_ADAM7 = 1 +}; + +/* Channels are always in byte-order */ +enum spng_format +{ + SPNG_FMT_RGBA8 = 1, + SPNG_FMT_RGBA16 = 2, + SPNG_FMT_RGB8 = 4, + + /* Partially implemented, see documentation */ + SPNG_FMT_GA8 = 16, + SPNG_FMT_GA16 = 32, + SPNG_FMT_G8 = 64, + + /* No conversion or scaling */ + SPNG_FMT_PNG = 256, /* host-endian */ + SPNG_FMT_RAW = 512 /* big-endian */ +}; + +enum spng_ctx_flags +{ + SPNG_CTX_IGNORE_ADLER32 = 1 /* Ignore checksum in DEFLATE streams */ +}; + +enum spng_decode_flags +{ + SPNG_DECODE_USE_TRNS = 1, /* Deprecated */ + SPNG_DECODE_USE_GAMA = 2, /* Deprecated */ + SPNG_DECODE_USE_SBIT = 8, /* Undocumented */ + + SPNG_DECODE_TRNS = 1, /* Apply transparency */ + SPNG_DECODE_GAMMA = 2, /* Apply gamma correction */ + SPNG_DECODE_PROGRESSIVE = 256 /* Initialize for progressive reads */ +}; + +enum spng_crc_action +{ + /* Default for critical chunks */ + SPNG_CRC_ERROR = 0, + + /* Discard chunk, invalid for critical chunks. + Since v0.6.2: default for ancillary chunks */ + SPNG_CRC_DISCARD = 1, + + /* Ignore and don't calculate checksum. + Since v0.6.2: also ignores checksums in DEFLATE streams */ + SPNG_CRC_USE = 2 +}; + +struct spng_ihdr +{ + uint32_t width; + uint32_t height; + uint8_t bit_depth; + uint8_t color_type; + uint8_t compression_method; + uint8_t filter_method; + uint8_t interlace_method; +}; + +struct spng_plte_entry +{ + uint8_t red; + uint8_t green; + uint8_t blue; + + uint8_t alpha; /* Reserved for internal use */ +}; + +struct spng_plte +{ + uint32_t n_entries; + struct spng_plte_entry entries[256]; +}; + +struct spng_trns +{ + uint16_t gray; + + uint16_t red; + uint16_t green; + uint16_t blue; + + uint32_t n_type3_entries; + uint8_t type3_alpha[256]; +}; + +struct spng_chrm_int +{ + uint32_t white_point_x; + uint32_t white_point_y; + uint32_t red_x; + uint32_t red_y; + uint32_t green_x; + uint32_t green_y; + uint32_t blue_x; + uint32_t blue_y; +}; + +struct spng_chrm +{ + double white_point_x; + double white_point_y; + double red_x; + double red_y; + double green_x; + double green_y; + double blue_x; + double blue_y; +}; + +struct spng_iccp +{ + char profile_name[80]; + size_t profile_len; + char *profile; +}; + +struct spng_sbit +{ + uint8_t grayscale_bits; + uint8_t red_bits; + uint8_t green_bits; + uint8_t blue_bits; + uint8_t alpha_bits; +}; + +struct spng_text +{ + char keyword[80]; + int type; + + size_t length; + char *text; + + uint8_t compression_flag; /* iTXt only */ + uint8_t compression_method; /* iTXt, ztXt only */ + char *language_tag; /* iTXt only */ + char *translated_keyword; /* iTXt only */ +}; + +struct spng_bkgd +{ + uint16_t gray; /* Only for gray/gray alpha */ + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t plte_index; /* Only for indexed color */ +}; + +struct spng_hist +{ + uint16_t frequency[256]; +}; + +struct spng_phys +{ + uint32_t ppu_x, ppu_y; + uint8_t unit_specifier; +}; + +struct spng_splt_entry +{ + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t alpha; + uint16_t frequency; +}; + +struct spng_splt +{ + char name[80]; + uint8_t sample_depth; + uint32_t n_entries; + struct spng_splt_entry *entries; +}; + +struct spng_time +{ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +struct spng_offs +{ + int32_t x, y; + uint8_t unit_specifier; +}; + +struct spng_exif +{ + size_t length; + char *data; +}; + +struct spng_chunk +{ + size_t offset; + uint32_t length; + uint8_t type[4]; + uint32_t crc; +}; + +typedef void* spng_malloc_fn(size_t size); +typedef void* spng_realloc_fn(void* ptr, size_t size); +typedef void* spng_calloc_fn(size_t count, size_t size); +typedef void spng_free_fn(void* ptr); + +struct spng_alloc +{ + spng_malloc_fn *malloc_fn; + spng_realloc_fn *realloc_fn; + spng_calloc_fn *calloc_fn; + spng_free_fn *free_fn; +}; + +struct spng_row_info +{ + uint32_t scanline_idx; + uint32_t row_num; /* deinterlaced row index */ + int pass; + uint8_t filter; +}; + +typedef struct spng_ctx spng_ctx; + +struct spng_subimage +{ + uint32_t width; + uint32_t height; + size_t out_width; /* byte width based on output format */ + size_t scanline_width; +}; + +struct spng_plte_entry16 +{ + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t alpha; +}; + +struct spng_chunk_bitfield +{ + unsigned ihdr : 1; + unsigned plte : 1; + unsigned chrm : 1; + unsigned iccp : 1; + unsigned gama : 1; + unsigned sbit : 1; + unsigned srgb : 1; + unsigned text : 1; + unsigned bkgd : 1; + unsigned hist : 1; + unsigned trns : 1; + unsigned phys : 1; + unsigned splt : 1; + unsigned time : 1; + unsigned offs : 1; + unsigned exif : 1; +}; + +struct decode_flags +{ + unsigned apply_trns : 1; + unsigned apply_gamma : 1; + unsigned use_sbit : 1; + unsigned indexed : 1; + unsigned do_scaling : 1; + unsigned interlaced : 1; + unsigned same_layout : 1; + unsigned zerocopy : 1; + unsigned unpack : 1; +}; + +typedef int spng_read_fn(spng_ctx* ctx, void* user, void* dest, size_t length); +typedef void spng__undo(spng_ctx* ctx); + +struct spng_ctx +{ + size_t data_size; + size_t bytes_read; + unsigned char* stream_buf; + const unsigned char* data; + + /* User-defined pointers for streaming */ + spng_read_fn* read_fn; + void* read_user_ptr; + + /* Used for buffer reads */ + const unsigned char* png_buf; /* base pointer for the buffer */ + size_t bytes_left; + size_t last_read_size; + + /* These are updated by read_header()/read_chunk_bytes() */ + struct spng_chunk current_chunk; + uint32_t cur_chunk_bytes_left; + uint32_t cur_actual_crc; + + struct spng_alloc alloc; + + int flags; /* context flags */ + int fmt; + + unsigned state : 4; + unsigned streaming : 1; + + unsigned encode_only : 1; + unsigned strict : 1; + unsigned discard : 1; + unsigned skip_crc : 1; + unsigned prev_was_idat : 1; + + spng__undo* undo; + + /* input file contains this chunk */ + struct spng_chunk_bitfield file; + + /* chunk was stored with spng_set_*() */ + struct spng_chunk_bitfield user; + + /* chunk was stored by reading or with spng_set_*() */ + struct spng_chunk_bitfield stored; + + /* used to reset the above in case of an error */ + struct spng_chunk_bitfield prev_stored; + + struct spng_chunk first_idat, last_idat; + + uint32_t max_width, max_height; + + size_t max_chunk_size; + size_t chunk_cache_limit; + size_t chunk_cache_usage; + + int crc_action_critical; + int crc_action_ancillary; + + struct spng_ihdr ihdr; + + struct spng_plte plte; + + struct spng_chrm_int chrm_int; + struct spng_iccp iccp; + + uint32_t gama; + + struct spng_sbit sbit; + + uint8_t srgb_rendering_intent; + + uint32_t n_text; + struct spng_text2* text_list; + + struct spng_bkgd bkgd; + struct spng_hist hist; + struct spng_trns trns; + struct spng_phys phys; + + uint32_t n_splt; + struct spng_splt* splt_list; + + struct spng_time time; + struct spng_offs offs; + struct spng_exif exif; + + struct spng_subimage subimage[7]; + + z_stream zstream; + unsigned char* scanline_buf, * prev_scanline_buf, * row_buf; + unsigned char* scanline, * prev_scanline, * row; + + size_t total_out_size; + size_t out_width; /* total_out_size / ihdr.height */ + + unsigned channels; + unsigned bytes_per_pixel; /* input PNG */ + unsigned pixel_size; /* output format */ + int widest_pass; + int last_pass; /* last non-empty pass */ + + uint16_t* gamma_lut; /* points to either _lut8 or _lut16 */ + uint16_t* gamma_lut16; + uint16_t gamma_lut8[256]; + unsigned char trns_px[8]; + struct spng_plte_entry16 decode_plte[256]; + struct spng_sbit decode_sb; + struct decode_flags decode_flags; + struct spng_row_info row_info; +}; + +SPNG_API spng_ctx *spng_ctx_new(int flags); +SPNG_API spng_ctx *spng_ctx_new2(struct spng_alloc *alloc, int flags); +SPNG_API void spng_ctx_free(spng_ctx *ctx); + +SPNG_API int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size); +SPNG_API int spng_set_png_stream(spng_ctx *ctx, spng_read_fn *read_fn, void *user); +SPNG_API int spng_set_png_file(spng_ctx *ctx, FILE *file); + +SPNG_API int spng_set_image_limits(spng_ctx *ctx, uint32_t width, uint32_t height); +SPNG_API int spng_get_image_limits(spng_ctx *ctx, uint32_t *width, uint32_t *height); + +SPNG_API int spng_set_chunk_limits(spng_ctx *ctx, size_t chunk_size, size_t cache_size); +SPNG_API int spng_get_chunk_limits(spng_ctx *ctx, size_t *chunk_size, size_t *cache_size); + +SPNG_API int spng_set_crc_action(spng_ctx *ctx, int critical, int ancillary); + +SPNG_API int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len); + +/* Decode */ +SPNG_API int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags); + +/* Progressive decode */ +SPNG_API int spng_decode_scanline(spng_ctx *ctx, void *out, size_t len); +SPNG_API int spng_decode_row(spng_ctx *ctx, void *out, size_t len); + +SPNG_API int spng_get_row_info(spng_ctx *ctx, struct spng_row_info *row_info); + +SPNG_API int spng_get_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); +SPNG_API int spng_get_plte(spng_ctx *ctx, struct spng_plte *plte); +SPNG_API int spng_get_trns(spng_ctx *ctx, struct spng_trns *trns); +SPNG_API int spng_get_chrm(spng_ctx *ctx, struct spng_chrm *chrm); +SPNG_API int spng_get_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); +SPNG_API int spng_get_gama(spng_ctx *ctx, double *gamma); +SPNG_API int spng_get_iccp(spng_ctx *ctx, struct spng_iccp *iccp); +SPNG_API int spng_get_sbit(spng_ctx *ctx, struct spng_sbit *sbit); +SPNG_API int spng_get_srgb(spng_ctx *ctx, uint8_t *rendering_intent); +SPNG_API int spng_get_text(spng_ctx *ctx, struct spng_text *text, uint32_t *n_text); +SPNG_API int spng_get_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); +SPNG_API int spng_get_hist(spng_ctx *ctx, struct spng_hist *hist); +SPNG_API int spng_get_phys(spng_ctx *ctx, struct spng_phys *phys); +SPNG_API int spng_get_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t *n_splt); +SPNG_API int spng_get_time(spng_ctx *ctx, struct spng_time *time); + +/* Official extensions */ +SPNG_API int spng_get_offs(spng_ctx *ctx, struct spng_offs *offs); +SPNG_API int spng_get_exif(spng_ctx *ctx, struct spng_exif *exif); + + +SPNG_API int spng_set_ihdr(spng_ctx *ctx, struct spng_ihdr *ihdr); +SPNG_API int spng_set_plte(spng_ctx *ctx, struct spng_plte *plte); +SPNG_API int spng_set_trns(spng_ctx *ctx, struct spng_trns *trns); +SPNG_API int spng_set_chrm(spng_ctx *ctx, struct spng_chrm *chrm); +SPNG_API int spng_set_chrm_int(spng_ctx *ctx, struct spng_chrm_int *chrm_int); +SPNG_API int spng_set_gama(spng_ctx *ctx, double gamma); +SPNG_API int spng_set_iccp(spng_ctx *ctx, struct spng_iccp *iccp); +SPNG_API int spng_set_sbit(spng_ctx *ctx, struct spng_sbit *sbit); +SPNG_API int spng_set_srgb(spng_ctx *ctx, uint8_t rendering_intent); +SPNG_API int spng_set_text(spng_ctx *ctx, struct spng_text *text, uint32_t n_text); +SPNG_API int spng_set_bkgd(spng_ctx *ctx, struct spng_bkgd *bkgd); +SPNG_API int spng_set_hist(spng_ctx *ctx, struct spng_hist *hist); +SPNG_API int spng_set_phys(spng_ctx *ctx, struct spng_phys *phys); +SPNG_API int spng_set_splt(spng_ctx *ctx, struct spng_splt *splt, uint32_t n_splt); +SPNG_API int spng_set_time(spng_ctx *ctx, struct spng_time *time); + +/* Official extensions */ +SPNG_API int spng_set_offs(spng_ctx *ctx, struct spng_offs *offs); +SPNG_API int spng_set_exif(spng_ctx *ctx, struct spng_exif *exif); + +SPNG_API const char *spng_strerror(int err); +SPNG_API const char *spng_version_string(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SPNG_H */