diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ff2c115 --- /dev/null +++ b/.clang-format @@ -0,0 +1,93 @@ +--- +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 2 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '' + Priority: 6 + - Regex: '(["/]PlatformDefs|"(system|system_gl|system_egl))\.h"' + Priority: 5 + - Regex: '"platform/[^/]+/' + Priority: 2 + - Regex: '^<[a-z0-9_]+>$' + Priority: 3 + - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>$' + Priority: 3 + - Regex: '^<' + Priority: 4 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '$' +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60000 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cb0aff..c19f60b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.18) project(visualization.pictureit) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) diff --git a/debian/copyright b/debian/copyright index 191bdbc..dff5cba 100644 --- a/debian/copyright +++ b/debian/copyright @@ -2,7 +2,7 @@ Format: http://dep.debian.net/deps/dep5 Upstream-Name: visualization.pictureit Files: * -Copyright: 2018-2021 Team Kodi +Copyright: 2018-2026 Team Kodi 2015-2019 LinuxWhatElse License: GPL-2+ This package is free software; you can redistribute it and/or modify @@ -23,7 +23,7 @@ License: GPL-2+ Files: debian/* -Copyright: 2019-2021 Team Kodi +Copyright: 2019-2026 Team Kodi License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/lib/kissfft/CMakeLists.txt b/lib/kissfft/CMakeLists.txt index 0417cf8..4f5daab 100644 --- a/lib/kissfft/CMakeLists.txt +++ b/lib/kissfft/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.18) project(kissfft) set(SOURCES kiss_fft.c diff --git a/lib/kissfft/kiss_fft_log.h b/lib/kissfft/kiss_fft_log.h index b5b631a..5012474 100644 --- a/lib/kissfft/kiss_fft_log.h +++ b/lib/kissfft/kiss_fft_log.h @@ -33,4 +33,4 @@ -#endif /* kiss_fft_log_h */ \ No newline at end of file +#endif /* kiss_fft_log_h */ diff --git a/lib/kodi-kissfft-note.txt b/lib/kodi-kissfft-note.txt index 3de21e6..262ab4d 100644 --- a/lib/kodi-kissfft-note.txt +++ b/lib/kodi-kissfft-note.txt @@ -1,5 +1,2 @@ -kissfft source from https://github.com/mborgerding/kissfft -Sync to 131.1.0 (16 Feb. 2021) - -TODO: As now also inside Ubuntu Packages (hirsute and impish) should it take next - time the library by normal depends build on addon. +kissfft from https://github.com/mborgerding/kissfft +Sync to version 131.2.0 (22. Oct. 2025) diff --git a/src/mrfft.cpp b/src/mrfft.cpp index fe88cbf..8217188 100644 --- a/src/mrfft.cpp +++ b/src/mrfft.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 Team Kodi (https://kodi.tv) + * Copyright (C) 2015-2026 Team Kodi (https://kodi.tv) * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSE.md for more information. @@ -12,60 +12,60 @@ #endif #include -MRFFT::MRFFT(int size, bool windowed) : - m_size(size), m_windowed(windowed) +MRFFT::MRFFT(int size, bool windowed) : m_size(size), m_windowed(windowed) { - m_cfg = kiss_fftr_alloc(m_size,0,nullptr,nullptr); + m_cfg = kiss_fftr_alloc(m_size, 0, nullptr, nullptr); } MRFFT::~MRFFT() { - // we don' use kiss_fftr_free here because - // its hardcoded to free and doesn't pay attention - // to SIMD (which might be used during kiss_fftr_alloc - //in the C'tor). - KISS_FFT_FREE(m_cfg); + // we don' use kiss_fftr_free here because + // its hardcoded to free and doesn't pay attention + // to SIMD (which might be used during kiss_fftr_alloc + //in the C'tor). + KISS_FFT_FREE(m_cfg); } void MRFFT::calc(const float* input, float* output) { - // temporary buffers - std::vector linput(m_size), rinput(m_size); - std::vector loutput(m_size), routput(m_size); + // temporary buffers + std::vector linput(m_size), rinput(m_size); + std::vector loutput(m_size), routput(m_size); - for (size_t i=0;i void MRFFT::hann(std::vector& data) { - for (size_t i=0;i //! \brief Class performing a RFFT of interleaved stereo data. class MRFFT { public: - //! \brief The constructor creates a RFFT plan. - //! \brief size Length of time data for a single channel. - //! \brief windowed Whether or not to apply a Hann window to data. - MRFFT(int size, bool windowed=false); - - //! \brief Free the RFFT plan - ~MRFFT(); - - //! \brief Calculate FFTs - //! \param input Input data of size 2*m_size - //! \param output Output data of size m_size. - void calc(const float* input, float* output); + //! \brief The constructor creates a RFFT plan. + //! \brief size Length of time data for a single channel. + //! \brief windowed Whether or not to apply a Hann window to data. + MRFFT(int size, bool windowed = false); + + //! \brief Free the RFFT plan + ~MRFFT(); + + //! \brief Calculate FFTs + //! \param input Input data of size 2*m_size + //! \param output Output data of size m_size. + void calc(const float* input, float* output); + protected: - //! \brief Apply a Hann window to a buffer. - //! \param data Vector with data to apply window to. - static void hann(std::vector& data); + //! \brief Apply a Hann window to a buffer. + //! \param data Vector with data to apply window to. + static void hann(std::vector& data); - size_t m_size; //!< Size for a single channel. - bool m_windowed; //!< Whether or not a Hann window is applied. - kiss_fftr_cfg m_cfg; //!< FFT plan + size_t m_size; //!< Size for a single channel. + bool m_windowed; //!< Whether or not a Hann window is applied. + kiss_fftr_cfg m_cfg; //!< FFT plan }; diff --git a/src/pictureit.cpp b/src/pictureit.cpp index 0472c29..1de11be 100644 --- a/src/pictureit.cpp +++ b/src/pictureit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Team Kodi (https://kodi.tv) + * Copyright (C) 2018-2026 Team Kodi (https://kodi.tv) * Copyright (C) 2015-2019 LinuxWhatElse * * SPDX-License-Identifier: GPL-2.0-or-later @@ -30,10 +30,56 @@ CVisPictureIt::CVisPictureIt() m_imgLoaderActive(false), m_imgLoaded(false) { + // Load settings on constuctor to have on "GetPresets(...)" call available. + m_presetsRootDir = kodi::addon::GetSettingString("presets_root_dir"); + m_updateOnNewTrack = kodi::addon::GetSettingBoolean("update_on_new_track"); + m_updateByInterval = kodi::addon::GetSettingBoolean("update_by_interval"); + m_imgUpdateInterval = kodi::addon::GetSettingInt("img_update_interval"); + m_fadeTimeMs = kodi::addon::GetSettingInt("fade_time_ms"); + m_visEnabled = kodi::addon::GetSettingBoolean("vis_enabled"); + m_visBgEnabled = kodi::addon::GetSettingBoolean("vis_bg_enabled"); + + m_visWidth = kodi::addon::GetSettingInt("vis_half_width"); + m_visWidth = m_visWidth * 1.0f / 100; + + m_visAnimationSpeed = kodi::addon::GetSettingInt("vis_animation_speed"); + m_visAnimationSpeed = m_visAnimationSpeed * 0.005f / 100; + + float scale[] = {1.0, 0.98, 0.96, 0.94, 0.92, 0.90, 0.88, 0.86, 0.84, 0.82, 0.80}; + m_visBottomEdge = scale[kodi::addon::GetSettingInt("vis_bottom_edge")]; +} + +bool CVisPictureIt::Init() +{ + if (!m_shadersLoaded) + { + std::string fraqShader = + kodi::addon::GetAddonPath("resources/shaders/" GL_TYPE_STRING "/frag.glsl"); + std::string vertShader = + kodi::addon::GetAddonPath("resources/shaders/" GL_TYPE_STRING "/vert.glsl"); + if (!LoadShaderFiles(vertShader, fraqShader) || !CompileAndLink()) + return false; + m_shadersLoaded = true; + } + + glGenBuffers(1, &m_vertexVBO); + glGenBuffers(1, &m_indexVBO); + + if (!m_dataLoader) + m_dataLoader = std::make_shared(&CVisPictureIt::load_data, this, m_presetsRootDir); + + m_initialized = true; + + return true; } -CVisPictureIt::~CVisPictureIt() +void CVisPictureIt::DeInit() { + if (!m_initialized) + return; + + m_initialized = false; + if (m_dataLoader != nullptr) { if (m_dataLoader->joinable()) @@ -60,30 +106,12 @@ CVisPictureIt::~CVisPictureIt() } m_piData.clear(); - glDeleteTextures(3, m_imgTextureIds); -} - -ADDON_STATUS CVisPictureIt::Create() -{ - m_presetsRootDir = kodi::addon::GetSettingString("presets_root_dir"); - - m_updateOnNewTrack = kodi::addon::GetSettingBoolean("update_on_new_track"); - m_updateByInterval = kodi::addon::GetSettingBoolean("update_by_interval"); - m_imgUpdateInterval = kodi::addon::GetSettingInt("img_update_interval"); - m_fadeTimeMs = kodi::addon::GetSettingInt("fade_time_ms"); - m_visEnabled = kodi::addon::GetSettingBoolean("vis_enabled"); - m_visBgEnabled = kodi::addon::GetSettingBoolean("vis_bg_enabled"); - - m_visWidth = kodi::addon::GetSettingInt("vis_half_width"); - m_visWidth = m_visWidth * 1.0f / 100; - - m_visAnimationSpeed = kodi::addon::GetSettingInt("vis_animation_speed"); - m_visAnimationSpeed = m_visAnimationSpeed * 0.005f / 100; - - float scale[] = {1.0, 0.98, 0.96, 0.94, 0.92, 0.90, 0.88, 0.86, 0.84, 0.82, 0.80}; - m_visBottomEdge = scale[kodi::addon::GetSettingInt("vis_bottom_edge")]; + glDeleteBuffers(1, &m_vertexVBO); + m_vertexVBO = 0; + glDeleteBuffers(1, &m_indexVBO); + m_indexVBO = 0; - return ADDON_STATUS_OK; + glDeleteTextures(3, m_imgTextureIds); } bool CVisPictureIt::GetPresets(std::vector& presets) @@ -127,42 +155,6 @@ bool CVisPictureIt::RandomPreset() return true; } -bool CVisPictureIt::Start(int iChannels, int iSamplesPerSec, - int iBitsPerSample, const std::string& szSongName) -{ - if (!m_shadersLoaded) - { - std::string fraqShader = kodi::addon::GetAddonPath("resources/shaders/" GL_TYPE_STRING "/frag.glsl"); - std::string vertShader = kodi::addon::GetAddonPath("resources/shaders/" GL_TYPE_STRING "/vert.glsl"); - if (!LoadShaderFiles(vertShader, fraqShader) || !CompileAndLink()) - return false; - m_shadersLoaded = true; - } - - glGenBuffers(1, &m_vertexVBO); - glGenBuffers(1, &m_indexVBO); - - if (!m_dataLoader) - m_dataLoader = std::make_shared(&CVisPictureIt::load_data, this, m_presetsRootDir); - - m_initialized = true; - - return true; -} - -void CVisPictureIt::Stop() -{ - if (!m_initialized) - return; - - m_initialized = false; - - glDeleteBuffers(1, &m_vertexVBO); - m_vertexVBO = 0; - glDeleteBuffers(1, &m_indexVBO); - m_indexVBO = 0; -} - bool CVisPictureIt::UpdateTrack(const kodi::addon::VisualizationTrack& track) { if (m_updateOnNewTrack) @@ -185,7 +177,7 @@ void CVisPictureIt::Render() m_updateImg = true; } - if (m_updateImg && ! m_dataLoaderActive) + if (m_updateImg && !m_dataLoaderActive) { kodi::Log(ADDON_LOG_DEBUG, "Requesting new image..."); m_updateImg = false; @@ -217,12 +209,13 @@ void CVisPictureIt::Render() glGenTextures(1, texture); glBindTexture(GL_TEXTURE_2D, texture[0]); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_imgWidth, m_imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_imgData); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_imgWidth, m_imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, + m_imgData); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); stbi_image_free(m_imgData); m_imgData = nullptr; @@ -231,7 +224,12 @@ void CVisPictureIt::Render() } m_fadeCurrent = 0.0f; - m_fadeOffsetMs = static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0) % m_fadeTimeMs; + m_fadeOffsetMs = + static_cast(std::chrono::duration( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() * + 1000.0) % + m_fadeTimeMs; } // If we are within a crossfade, fade out the current image @@ -246,7 +244,14 @@ void CVisPictureIt::Render() if (m_fadeOffsetMs && m_fadeCurrent < 1.0f) { - m_fadeCurrent = ((float) ((static_cast(std::chrono::duration(std::chrono::high_resolution_clock::now().time_since_epoch()).count() * 1000.0) - m_fadeOffsetMs) % m_fadeTimeMs) / m_fadeTimeMs); + m_fadeCurrent = ((float)((static_cast( + std::chrono::duration( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() * + 1000.0) - + m_fadeOffsetMs) % + m_fadeTimeMs) / + m_fadeTimeMs); if (m_fadeCurrent < m_fadeLast) { m_fadeLast = 0.0f; @@ -277,14 +282,17 @@ void CVisPictureIt::Render() if (m_visBgEnabled) { sLight framedTextures[4]; - framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = framedTextures[3].color = sColor(0.0f, 0.0f, 0.0f, 0.7f); - framedTextures[0].vertex = sPosition(1.0f, (m_visBottomEdge - m_visBarMaxHeight) - (1.0f - m_visBottomEdge)); - framedTextures[1].vertex = sPosition(-1.0f,(m_visBottomEdge - m_visBarMaxHeight) - (1.0f - m_visBottomEdge)); + framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = + framedTextures[3].color = sColor(0.0f, 0.0f, 0.0f, 0.7f); + framedTextures[0].vertex = + sPosition(1.0f, (m_visBottomEdge - m_visBarMaxHeight) - (1.0f - m_visBottomEdge)); + framedTextures[1].vertex = + sPosition(-1.0f, (m_visBottomEdge - m_visBarMaxHeight) - (1.0f - m_visBottomEdge)); framedTextures[2].vertex = sPosition(-1.0f, 1.0f); - framedTextures[3].vertex = sPosition( 1.0f, 1.0f); + framedTextures[3].vertex = sPosition(1.0f, 1.0f); glEnable(GL_BLEND); - glBufferData(GL_ARRAY_BUFFER, sizeof(sLight)*4, framedTextures, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(sLight) * 4, framedTextures, GL_STATIC_DRAW); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); glDisable(GL_BLEND); } @@ -306,7 +314,7 @@ void CVisPictureIt::Render() x1 = x1 + (bar_width / 4); x2 = x2 - (bar_width / 4); - draw_bars((i-1), x1, x2); + draw_bars((i - 1), x1, x2); } DisableShader(); @@ -330,7 +338,7 @@ void CVisPictureIt::AudioData(const float* pAudioData, size_t iAudioDataLength) // So I just copied the "rfft.h" and "rfft.cpp", renamed the classe to "MRFFT" (otherwise we'd use the original) and set // the flag to "true". // Further this gives us the ability to change the response if needed (They return the magnitude per default I believe) - if (m_prevFreqDataLength != iFreqDataLength || ! m_tranform) + if (m_prevFreqDataLength != iFreqDataLength || !m_tranform) { m_tranform.reset(new MRFFT(iFreqDataLength, true)); m_prevFreqDataLength = iFreqDataLength; @@ -365,7 +373,6 @@ void CVisPictureIt::AudioData(const float* pAudioData, size_t iAudioDataLength) delete[] freq_data; } - std::string CVisPictureIt::path_join(std::string a, std::string b) { /** @@ -387,14 +394,17 @@ std::string CVisPictureIt::path_join(std::string a, std::string b) // b ends with "/" if (b.substr(b.length() - 1, b.length()) == "/") { - b = b.substr(0, b.size() -1); + b = b.substr(0, b.size() - 1); } return a + "/" + b; } -bool CVisPictureIt::list_dir(const std::string& path, td_vec_str &store, bool recursive, - bool incl_full_path, std::string file_filter) +bool CVisPictureIt::list_dir(const std::string& path, + td_vec_str& store, + bool recursive, + bool incl_full_path, + std::string file_filter) { std::vector items; if (!kodi::vfs::GetDirectory(path, file_filter, items)) @@ -443,7 +453,8 @@ int CVisPictureIt::get_next_img_pos() std::uniform_int_distribution dist(0, m_piImages.size() - 1); int num = dist(engine); - if (num == m_imgCurrentPos && m_get_next_img_pos_Calls++ < 10) // try only 10 times to prevent possible dead loop + if (num == m_imgCurrentPos && + m_get_next_img_pos_Calls++ < 10) // try only 10 times to prevent possible dead loop return get_next_img_pos(); m_get_next_img_pos_Calls = 0; @@ -486,7 +497,7 @@ void CVisPictureIt::load_data(const std::string& path) } td_vec_str images; - if (m_piPresets[0] == "Default" ) + if (m_piPresets[0] == "Default") { list_dir(path, images, true, true, img_filter); m_piData[m_piPresets[0]] = images; @@ -587,11 +598,13 @@ void CVisPictureIt::draw_image(GLuint img_tex_id, float opacity) if (!img_tex_id) { - framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = framedTextures[3].color = sColor(0.0f, 0.0f, 0.0f, opacity); + framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = + framedTextures[3].color = sColor(0.0f, 0.0f, 0.0f, opacity); } else { - framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = framedTextures[3].color = sColor(1.0f, 1.0f, 1.0f, opacity); + framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = + framedTextures[3].color = sColor(1.0f, 1.0f, 1.0f, opacity); } framedTextures[0].vertex = sPosition(-1.0f, -1.0f); @@ -604,7 +617,7 @@ void CVisPictureIt::draw_image(GLuint img_tex_id, float opacity) framedTextures[3].coord = sCoord(0.0f, 1.0f); m_textureUsed = true; EnableShader(); - glBufferData(GL_ARRAY_BUFFER, sizeof(sLight)*4, framedTextures, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(sLight) * 4, framedTextures, GL_STATIC_DRAW); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); DisableShader(); @@ -640,20 +653,21 @@ void CVisPictureIt::draw_bars(int i, GLfloat x1, GLfloat x2) GLfloat y2 = m_visBottomEdge - m_cvisBarHeights[i]; sLight framedTextures[4]; - framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = framedTextures[3].color = sColor(1.0f, 1.0f, 1.0f, 1.0f); - - framedTextures[0].vertex = sPosition(x1, y2); // Top Left - framedTextures[1].vertex = sPosition(x2, y2); // Top Right - framedTextures[2].vertex = sPosition(x2, m_visBottomEdge); // Bottom Right - framedTextures[3].vertex = sPosition(x1, m_visBottomEdge); // Bottom Left - glBufferData(GL_ARRAY_BUFFER, sizeof(sLight)*4, framedTextures, GL_STATIC_DRAW); + framedTextures[0].color = framedTextures[1].color = framedTextures[2].color = + framedTextures[3].color = sColor(1.0f, 1.0f, 1.0f, 1.0f); + + framedTextures[0].vertex = sPosition(x1, y2); // Top Left + framedTextures[1].vertex = sPosition(x2, y2); // Top Right + framedTextures[2].vertex = sPosition(x2, m_visBottomEdge); // Bottom Right + framedTextures[3].vertex = sPosition(x1, m_visBottomEdge); // Bottom Left + glBufferData(GL_ARRAY_BUFFER, sizeof(sLight) * 4, framedTextures, GL_STATIC_DRAW); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); - framedTextures[0].vertex = sPosition(-x2, y2); // Top Left - framedTextures[1].vertex = sPosition(-x1, y2); // Top Right - framedTextures[2].vertex = sPosition(-x1, m_visBottomEdge); // Bottom Right - framedTextures[3].vertex = sPosition(-x2, m_visBottomEdge); // Bottom Left - glBufferData(GL_ARRAY_BUFFER, sizeof(sLight)*4, framedTextures, GL_STATIC_DRAW); + framedTextures[0].vertex = sPosition(-x2, y2); // Top Left + framedTextures[1].vertex = sPosition(-x1, y2); // Top Right + framedTextures[2].vertex = sPosition(-x1, m_visBottomEdge); // Bottom Right + framedTextures[3].vertex = sPosition(-x2, m_visBottomEdge); // Bottom Left + glBufferData(GL_ARRAY_BUFFER, sizeof(sLight) * 4, framedTextures, GL_STATIC_DRAW); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); } @@ -667,15 +681,18 @@ void CVisPictureIt::start_render() glBindBuffer(GL_ARRAY_BUFFER, m_vertexVBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexVBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, m_index, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, m_index, GL_STATIC_DRAW); - glVertexAttribPointer(m_hVertex, 4, GL_FLOAT, GL_TRUE, sizeof(sLight), BUFFER_OFFSET(offsetof(sLight, vertex))); + glVertexAttribPointer(m_hVertex, 4, GL_FLOAT, GL_TRUE, sizeof(sLight), + BUFFER_OFFSET(offsetof(sLight, vertex))); glEnableVertexAttribArray(m_hVertex); - glVertexAttribPointer(m_hColor, 4, GL_FLOAT, GL_TRUE, sizeof(sLight), BUFFER_OFFSET(offsetof(sLight, color))); + glVertexAttribPointer(m_hColor, 4, GL_FLOAT, GL_TRUE, sizeof(sLight), + BUFFER_OFFSET(offsetof(sLight, color))); glEnableVertexAttribArray(m_hColor); - glVertexAttribPointer(m_hCoord, 2, GL_FLOAT, GL_TRUE, sizeof(sLight), BUFFER_OFFSET(offsetof(sLight, coord))); + glVertexAttribPointer(m_hCoord, 2, GL_FLOAT, GL_TRUE, sizeof(sLight), + BUFFER_OFFSET(offsetof(sLight, coord))); glEnableVertexAttribArray(m_hCoord); } diff --git a/src/pictureit.h b/src/pictureit.h index 43eef69..2a12d9b 100644 --- a/src/pictureit.h +++ b/src/pictureit.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2021 Team Kodi (https://kodi.tv) + * Copyright (C) 2018-2026 Team Kodi (https://kodi.tv) * Copyright (C) 2015-2019 LinuxWhatElse * * SPDX-License-Identifier: GPL-2.0-or-later @@ -8,34 +8,34 @@ #pragma once -#include -#include -#include -#include - #include #include #include +#include +#include +#include +#include + struct sPosition { sPosition() : x(0.0f), y(0.0f), z(0.0f), u(1.0f) {} sPosition(float x, float y, float z = 0.0f) : x(x), y(y), z(z), u(1.0f) {} - float x,y,z,u; + float x, y, z, u; }; struct sCoord { sCoord() : u(0.0f), v(0.0f) {} sCoord(float u, float v) : u(u), v(v) {} - float u,v; + float u, v; }; struct sColor { sColor() : r(0.0f), g(0.0f), b(0.0f), a(1.0f) {} sColor(float r, float g, float b, float a = 1.0f) : r(r), g(g), b(b), a(a) {} - float r,g,b,a; + float r, g, b, a; }; struct sLight @@ -56,18 +56,16 @@ class ATTR_DLL_LOCAL CVisPictureIt : public kodi::addon::CAddonBase, { public: CVisPictureIt(); - ~CVisPictureIt() override; + ~CVisPictureIt() override = default; - ADDON_STATUS Create() override; + bool Init() override; + void DeInit() override; bool GetPresets(std::vector& presets) override; int GetActivePreset() override; bool PrevPreset() override; bool NextPreset() override; bool LoadPreset(int select) override; bool RandomPreset() override; - bool Start(int channels, int samplesPerSec, int bitsPerSample, - const std::string& songName) override; - void Stop() override; void Render() override; void AudioData(const float* audioData, size_t audioDataLength) override; bool UpdateTrack(const kodi::addon::VisualizationTrack& track) override; @@ -78,8 +76,11 @@ class ATTR_DLL_LOCAL CVisPictureIt : public kodi::addon::CAddonBase, private: std::string path_join(std::string a, std::string b); - bool list_dir(const std::string& path, td_vec_str &store, bool recursive = false, - bool incl_full_path = true, std::string file_filter = ""); + bool list_dir(const std::string& path, + td_vec_str& store, + bool recursive = false, + bool incl_full_path = true, + std::string file_filter = ""); int get_next_img_pos(); void load_presets(const std::string& path); void load_data(const std::string& path); @@ -172,10 +173,8 @@ class ATTR_DLL_LOCAL CVisPictureIt : public kodi::addon::CAddonBase, // The max height for each bar const GLfloat m_visBarMaxHeight = 0.18f; - const float m_visBottomEdgeScale[11] = - { - 1.0, 0.98, 0.96, 0.94, 0.92, 0.90, 0.88, 0.86, 0.84, 0.82, 0.80 - }; + const float m_visBottomEdgeScale[11] = {1.0, 0.98, 0.96, 0.94, 0.92, 0.90, + 0.88, 0.86, 0.84, 0.82, 0.80}; // Whatever we get from AudioData GLfloat m_visBarHeights[m_visBarCount] = {}; diff --git a/src/stb_image.h b/src/stb_image.h index d60371b..9eedabe 100644 --- a/src/stb_image.h +++ b/src/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,9 @@ LICENSE RECENT REVISION HISTORY: + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings @@ -108,7 +111,7 @@ RECENT REVISION HISTORY: Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 Brad Weinberger Matvey Cherevko github:mosra @@ -140,7 +143,7 @@ RECENT REVISION HISTORY: // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) +// stbi_image_free(data); // // Standard parameters: // int *x -- outputs image width in pixels @@ -635,7 +638,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #endif #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; @@ -1063,6 +1066,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) } #endif +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -1985,9 +2005,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) int i,j,k=0; unsigned int code; // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } h->size[k] = 0; // compute actual symbols (from jpeg spec) @@ -2112,6 +2135,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol @@ -2130,6 +2155,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) unsigned int k; int sgn; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) k = stbi_lrot(j->code_buffer, n); @@ -2144,6 +2170,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; @@ -2155,6 +2182,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; @@ -2192,8 +2220,10 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec @@ -2207,6 +2237,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location @@ -2246,8 +2277,10 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient @@ -2282,6 +2315,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; @@ -3102,6 +3136,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m) sizes[i] = stbi__get8(z->s); n += sizes[i]; } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; @@ -3351,6 +3386,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { @@ -3367,25 +3424,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } + j->marker = stbi__skip_jpeg_junk_at_end(j); // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); } else { - if (!stbi__process_marker(j, m)) return 0; + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); } - m = stbi__get_marker(j); } if (j->progressive) stbi__jpeg_finish(j); @@ -3976,6 +4030,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); @@ -3989,6 +4044,7 @@ static int stbi__jpeg_test(stbi__context *s) int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); @@ -4014,6 +4070,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); @@ -4121,6 +4178,7 @@ typedef struct { stbi_uc *zbuffer, *zbuffer_end; int num_bits; + int hit_zeof_once; stbi__uint32 code_buffer; char *zout; @@ -4187,9 +4245,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) int b,s; if (a->num_bits < 16) { if (stbi__zeof(a)) { - return -1; /* report error for unexpected end of data. */ + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); } - stbi__fill_bits(a); } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { @@ -4254,17 +4323,25 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) int len,dist; if (z == 256) { a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } return 1; } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (zout + len > a->zout_end) { + if (len > a->zout_end - zout) { if (!stbi__zexpand(a, zout, len)) return 0; zout = a->zout; } @@ -4408,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; + a->hit_zeof_once = 0; do { final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); @@ -4563,9 +4641,8 @@ enum { STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - // synthetic filters used for first scanline to avoid needing a dummy row of 0s - STBI__F_avg_first, - STBI__F_paeth_first + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first }; static stbi_uc first_row_filter[5] = @@ -4574,29 +4651,56 @@ static stbi_uc first_row_filter[5] = STBI__F_sub, STBI__F_none, STBI__F_avg_first, - STBI__F_paeth_first + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub }; static int stbi__paeth(int a, int b, int c) { - int p = a + b - c; - int pa = abs(p-a); - int pb = abs(p-b); - int pc = abs(p-c); - if (pa <= pb && pa <= pc) return a; - if (pb <= pc) return b; - return c; + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; } static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); + int bytes = (depth == 16 ? 2 : 1); stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n*bytes; stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; int k; int img_n = s->img_n; // copy it into a local for later @@ -4608,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); img_len = (img_width_bytes + 1) * y; // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, @@ -4617,189 +4724,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // so just check for raw_len < img_len always. if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; int filter = *raw++; - if (filter > 4) - return stbi__err("invalid filter","Corrupt PNG"); - - if (depth < 8) { - if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); - cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place - filter_bytes = 1; - width = img_width_bytes; + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first byte explicitly - for (k=0; k < filter_bytes; ++k) { - switch (filter) { - case STBI__F_none : cur[k] = raw[k]; break; - case STBI__F_sub : cur[k] = raw[k]; break; - case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; - case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; - case STBI__F_avg_first : cur[k] = raw[k]; break; - case STBI__F_paeth_first: cur[k] = raw[k]; break; - } - } - - if (depth == 8) { - if (img_n != out_n) - cur[img_n] = 255; // first pixel - raw += img_n; - cur += out_n; - prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; - } else { - raw += 1; - cur += 1; - prior += 1; + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; } - // this is a little gross, so that we don't switch per-pixel or per-component - if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ - case f: \ - for (k=0; k < nk; ++k) - switch (filter) { - // "none" filter turns into a memcpy here; make that explicit. - case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; - } - #undef STBI__CASE - raw += nk; - } else { - STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ - case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) - switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } - } - } - } + raw += nk; - // we make a separate pass to expand bits to pixels; for performance, - // this could run two scanlines behind the above code, so it won't - // intefere with filtering but will still be in the cache. - if (depth < 8) { - for (j=0; j < y; ++j) { - stbi_uc *cur = a->out + stride*j; - stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; - // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit - // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; - // note that the final byte might overshoot and write more data than desired. - // we can allocate enough data that this never writes out of memory, but it - // could also overwrite the next scanline. can it overwrite non-empty data - // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. - // so we need to explicitly clamp the final ones - + // expand bits to bytes first if (depth == 4) { - for (k=x*img_n; k >= 2; k-=2, ++in) { - *cur++ = scale * ((*in >> 4) ); - *cur++ = scale * ((*in ) & 0x0f); + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; } - if (k > 0) *cur++ = scale * ((*in >> 4) ); } else if (depth == 2) { - for (k=x*img_n; k >= 4; k-=4, ++in) { - *cur++ = scale * ((*in >> 6) ); - *cur++ = scale * ((*in >> 4) & 0x03); - *cur++ = scale * ((*in >> 2) & 0x03); - *cur++ = scale * ((*in ) & 0x03); + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; } - if (k > 0) *cur++ = scale * ((*in >> 6) ); - if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); - if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); - } else if (depth == 1) { - for (k=x*img_n; k >= 8; k-=8, ++in) { - *cur++ = scale * ((*in >> 7) ); - *cur++ = scale * ((*in >> 6) & 0x01); - *cur++ = scale * ((*in >> 5) & 0x01); - *cur++ = scale * ((*in >> 4) & 0x01); - *cur++ = scale * ((*in >> 3) & 0x01); - *cur++ = scale * ((*in >> 2) & 0x01); - *cur++ = scale * ((*in >> 1) & 0x01); - *cur++ = scale * ((*in ) & 0x01); + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; } - if (k > 0) *cur++ = scale * ((*in >> 7) ); - if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); - if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); - if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); - if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); - if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); - if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); } - if (img_n != out_n) { - int q; - // insert alpha = 255 - cur = a->out + stride*j; + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); if (img_n == 1) { - for (q=x-1; q >= 0; --q) { - cur[q*2+1] = 255; - cur[q*2+0] = cur[q]; + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; } } else { STBI_ASSERT(img_n == 3); - for (q=x-1; q >= 0; --q) { - cur[q*4+3] = 255; - cur[q*4+2] = cur[q*3+2]; - cur[q*4+1] = cur[q*3+1]; - cur[q*4+0] = cur[q*3+0]; + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; } } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } + STBI_FREE(filter_buf); + if (!all_ok) return 0; + return 1; } @@ -4955,7 +5010,7 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; -STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) { stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; stbi__unpremultiply_on_load_set = 1; @@ -5064,14 +5119,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS } + // even with SCAN_header, have to scan to see if we have a tRNS break; } @@ -5103,10 +5157,14 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger } } break; @@ -5115,7 +5173,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; @@ -5498,8 +5562,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { - if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); } } @@ -7187,12 +7265,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re // Run value = stbi__get8(s); count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } @@ -7446,10 +7524,17 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; @@ -7486,6 +7571,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c) while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); } return value; @@ -7516,9 +7603,13 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value diff --git a/visualization.pictureit/addon.xml.in b/visualization.pictureit/addon.xml.in index 14f0ddd..e766b44 100644 --- a/visualization.pictureit/addon.xml.in +++ b/visualization.pictureit/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@